[project] name = "intentkit" version = "0.0.0" description = "Intent-based AI Agent Platform - Core Package" authors = [ { name = "Ruihua", email = "ruihua@crestal.network" }, ] requires-python = "==3.13.*" readme = "intentkit/README.md" license = { file = "intentkit/LICENSE" } dependencies = [ # AWS "aws-secretsmanager-caching>=1.1.3", "boto3>=1.37.23,<2.0.0", "botocore>=1.35.97", "mypy-boto3-s3>=1.37.24,<2.0.0", # Web framework "fastapi>=0.115.8", "starlette>=0.47.1", "httpx>=0.28.1", "httpcore>=1.0.9", # The second HTTP stack: mcp 2.x, openai 3.x and anthropic 1.x run on # httpx2/httpcore2. # We hand mcp an httpx2 client (clients/mcp) and classify both stacks' # transport errors in utils/error.py; our own code otherwise stays on httpx. "httpx2>=2.5.0", "httpcore2>=2.10.0", "aiohttp>=3.14.0", # Database "psycopg>=3.2.9", "psycopg-pool>=3.2.4", "asyncpg>=0.30.0", "aiosqlite>=0.21.0", "sqlalchemy[asyncio]>=2.0.37", # Schema migrations run automatically at startup (config/migration.py) "alembic>=1.14.0", # 8.x defaults socket_timeout/socket_connect_timeout to 5s and RESP3; we set # both timeouts explicitly in config/redis.py so the bump changes nothing. "redis>=8.0.1,<9.0.0", # LangChain & LLM "langchain>=1.3,<2", "langchain-community>=0.4,<1", # 1.6 added the provider-neutral ModelError vocabulary (is_retryable), # which core/executor.py and utils/error.py classify against. "langchain-core>=1.6,<2", "langchain-openai>=1.2,<2", "langchain-text-splitters>=1.1,<2", "langchain-deepseek>=1.0,<2", # 4.3.5 is the release that adopted langchain-core 1.6's ModelError # mixins (GoogleRateLimitError etc.); the Gemini path carries no HTTP # status of its own, so below that the retry predicate is blind to it. "langchain-google-genai>=4.3.5,<5", "langchain-anthropic>=1.4,<2", # Declared only because utils/error.py imports APITimeoutError directly # (deptry DEP003). langchain-anthropic pins the effective version — keep # this floor loose so it can never outrank that pin. 1.0 is where the SDK # moved to httpx2, which the timeout tests build their requests with. "anthropic>=1.0.0", "langchain-openrouter>=0.2.5,<1", # Used directly for image generation (tools/image, core/avatar) and for its # typed error classes; chat and usage accounting go through # langchain-openrouter. Deliberately uncapped: langchain-openrouter already # caps it at <1.0.0, so lifting that cap upstream lets us follow the 1.x # line without editing this line. "openrouter>=0.11.46", "google-genai>=2.8.0", "langgraph>=1.2,<2", "langgraph-checkpoint>=4.1,<5", "langgraph-checkpoint-postgres>=3.1,<4", "langgraph-prebuilt>=1.1,<2", "openai>=1.59.6", "ormsgpack>=1.12.2", # Blockchain / Web3 "cdp-sdk>=1.31.1", "web3>=7.10.0", "eth-abi>=5.0.0", "eth-account>=0.13.7", "eth-typing>=6.0.0", "eth-utils>=2.1.0", "hexbytes>=1.3.1", # 2.20 turned client spend controls on by default ($1/payment, SDK # default assets only); tools/x402 disables them via set_spend_controls, # which only exists from 2.20 on. "x402>=2.20.0", # Social platforms "slack-sdk>=3.34.0", # Utilities "cron-validator>=1.0.8,<2.0.0", "epyxid>=0.3.3", "filetype>=1.2.0,<2.0.0", "pillow>=11.1.0,<13.0.0", "pydantic>=2.10.6", "python-dotenv>=1.0.1", "pytz>=2025.1", "pyyaml>=6.0.2", # Scheduling (core/scheduler.py) "apscheduler>=3.11.0", # Utilities (direct imports of pydantic/web3 companions) "cryptography>=48.0.0", # Declared only because tools/base.py imports PydanticUndefined directly # (deptry DEP003). pydantic pins pydantic-core exactly, so a tight floor # here silently becomes a floor on pydantic itself and can outrank an AI # SDK's own pydantic ceiling — keep it as loose as the API we use allows. "pydantic-core>=2.33.1", # MCP (Model Context Protocol) # 2.x carries the 2026-07-28 spec revision, but keeps the pre-revision # protocols alongside it (SUPPORTED_PROTOCOL_VERSIONS still spans # 2024-11-05..2026-07-28) and keeps the SSE transport. ClientSession. # initialize() deliberately negotiates LATEST_HANDSHAKE_VERSION, so our # clients stay on the legacy handshake and old servers keep working. "mcp>=2.0.0", # Chinese A-share market data (cn_stock tool) "akshare>=1.16.0", "langfuse>=4.7,<5", ] keywords = ["ai", "agent", "intent", "blockchain", "crypto"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Scientific/Engineering :: Artificial Intelligence", ] [project.optional-dependencies] # PDF rendering (utils/pdf.py imports these lazily; weasyprint needs system # pango/cairo, so it stays out of the default install). 70.0 is the release # that turned url_fetcher into the URLFetcher class the SSRF guard subclasses # (and the fix for GHSA-jf6q-chmf-3h3v). pdf = [ "weasyprint>=70.0", "markdown>=3.7", "pymdown-extensions>=10.0", ] # Ollama provider support (models/llm.py imports langchain_ollama lazily) ollama = [ "langchain-ollama>=1.1,<2", ] [project.urls] Homepage = "https://github.com/crestalnetwork/intentkit" Repository = "https://github.com/crestalnetwork/intentkit" Documentation = "https://github.com/crestalnetwork/intentkit/tree/main/docs" "Bug Tracker" = "https://github.com/crestalnetwork/intentkit/issues" [dependency-groups] # App runtime dependencies (app/ folder needs these beyond intentkit) app = [ "gunicorn>=23.0.0", "uvicorn>=0.34.0,<1.0.0", "pydantic-settings>=2.8.1", "python-multipart>=0.0.20", "sentry-sdk[fastapi]>=2.20.0", # PDF generation (the pdf extra, pinned identically) "weasyprint>=70.0", "markdown>=3.7", "pymdown-extensions>=10.0", "pyjwt>=2.13.0", ] # Development-only dependencies (testing, linting, local dev tools) dev = [ # Include app dependencies for local development { include-group = "app" }, # Testing "pytest>=8.4.0", "pytest-asyncio>=1.2.0", "testing.postgresql", # Linting and code quality (lint config inherits ruff 0.16's default rule set) "ruff>=0.16.0", "deptry>=0.23.0", # Local LLM testing "langchain-ollama>=1.1,<2", "basedpyright>=1.39.7", "import-linter>=2.11", ] [tool.uv] package = true default-groups = ["dev"] # Override bcl to >=3.0.0 because 2.x lacks Linux ARM64 (aarch64) wheels, # causing source builds to fail on Python 3.13 (pkgutil.ImpImporter removed). # bcl 3.0.0 has manylinux_2_17_aarch64 wheels that work on ARM64 Docker builds. override-dependencies = ["bcl>=3.0.0"] [tool.basedpyright] # scripts/ holds one-off ops/migration scripts with loose typing; the # autogenerated migration revisions are in the same category; not worth checking exclude = ["scripts", "intentkit/migrations/versions", "**/node_modules", "**/__pycache__", "**/.*", "dist", "build"] reportAny = false reportExplicitAny = false reportMissingTypeStubs = false reportCallInDefaultInitializer = false reportUnknownMemberType = false reportUnknownVariableType = false reportUnknownArgumentType = false reportImplicitStringConcatenation = false reportUnusedCallResult = false reportMissingParameterType = false reportUnknownParameterType = false reportImplicitOverride = false reportUnusedParameter = false reportUnannotatedClassAttribute = false reportUnknownLambdaType = false reportMissingTypeArgument = false reportUnnecessaryIsInstance = false reportUnnecessaryComparison = false # Tests legitimately reach into private members; keep those rules library-only [[tool.basedpyright.executionEnvironments]] root = "tests" reportPrivateUsage = false reportPrivateLocalImportUsage = false [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["intentkit"] [tool.hatch.build.targets.sdist] include = [ "intentkit", "pyproject.toml", ] [tool.ruff.format] # Ruff 0.16 formats Python code blocks inside Markdown by default. Our docs use # illustrative fragments (bare kwargs, partial function bodies), which the # formatter rewrites into misleading full-module style — so keep .md as authored. exclude = ["*.md"] [tool.ruff.lint] # Ruff 0.16 replaced the old E4/E7/E9/F default with a curated 413-rule set # (partial-prefix subsets across ~35 groups). We inherit that default instead of # pinning: new defaults from future ruff versions are adopted on upgrade, and # anything we can't pass goes into `ignore` below with a reason. extend-select = [ # The pre-0.16 pycodestyle baseline, no longer part of ruff's defaults. "E4", "E7", "E9", # Full isort (defaults only carry I001). "I", # Blocking subprocess/os calls in async defs; adopted pre-0.16 but not in # the curated default set. "ASYNC240", "ASYNC250", ] ignore = [ # Broad `except Exception` is deliberate resilience in a tool-execution # platform: agent/tool failures must degrade, never crash the run loop. "BLE001", # The whole repo (credit system, pricing, tools, tests — 300+ sites) # uniformly uses the str constructor for Decimal (`Decimal("100")` and # `Decimal("0.0001")` alike). FURB157 would rewrite only the int-valued # ones, splitting the style in financial code. "FURB157", ] [tool.ruff.lint.flake8-bugbear] # FastAPI's parameter markers are designed to be called in defaults. extend-immutable-calls = [ "fastapi.Body", "fastapi.Depends", "fastapi.File", "fastapi.Path", "fastapi.Query", ] [tool.ruff.lint.per-file-ignores] # One-off ops/migration scripts run interactively from a shell; blocking I/O, # prompts on the event loop, and naive local timestamps are fine there # (scripts/ is likewise out of scope for deptry and basedpyright). "scripts/*" = ["ASYNC", "LOG015", "DTZ"] # Nested `with patch(...)` stacks mirror decorator stacking and read better # than one parenthesized mega-with; naive datetimes are fine for fixture data; # silently swallowed exceptions in best-effort test cleanup are fine too. "tests/*" = ["SIM117", "DTZ", "S110", "S112"] [tool.ruff.lint.isort] force-single-line = false known-first-party = ["intentkit"] known-local-folder = ["app"] section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"] [tool.deptry] known_first_party = ["intentkit"] # scripts/ are one-off ops tools run in the dev env; tests run under dev deps; # .claude may hold throwaway git worktrees (separate checkouts of this repo) exclude = ["scripts", "tests", "frontend", "docs", "\\.venv", "\\.claude"] [tool.deptry.per_rule_ignores] # asyncpg/aiosqlite are loaded dynamically by SQLAlchemy as DB drivers; # pymdown-extensions is loaded by name through the markdown extension registry DEP002 = ["asyncpg", "aiosqlite", "pymdown-extensions"] # app/ legitimately uses the app dependency group (deptry treats groups as dev) DEP004 = ["sentry_sdk", "jwt"] # Enforces the import dependency order documented in CLAUDE.md. # Each layer may import the ones below it, never above. [tool.importlinter] root_package = "intentkit" [[tool.importlinter.contracts]] name = "intentkit layered architecture" type = "layers" layers = [ "intentkit.core", "intentkit.tools", "intentkit.wallets", "intentkit.clients", "intentkit.abstracts", "intentkit.models", "intentkit.config", "intentkit.utils", ] [tool.pytest.ini_options] pythonpath = ["."] testpaths = ["tests"] markers = [ "bdd: mark test as BDD integration test", "integration: hits live external APIs; excluded from the default CI run", ] asyncio_default_fixture_loop_scope = "session" asyncio_default_test_loop_scope = "session"