Skip to main content

Vendor-neutral agent runtime: one protocol, pluggable adapters for Claude Code, GitHub Copilot, Moonshot Kimi, AWS Bedrock, the OpenCode HTTP agent server, and OpenAI-compatible gateways.

Project description

airframe

PyPI version Python versions License: MIT CI

One protocol, every agent SDK. Vendor-neutral runtime for Python AI agents — write once against a small AgentRuntime protocol and run on AWS Bedrock, Claude Code, GitHub Copilot, Moonshot Kimi, the OpenCode HTTP agent server, OpenCode Go, OpenCode Zen, or OpenRouter by changing a single config value.

Quickstart

pip install airframe-agents[claude]   # or [bedrock] / [copilot] / [kimi] / [openai-compat] / [all]
from airframe import runtime_for, ProviderModel
from pydantic import BaseModel

class Brief(BaseModel):
    summary: str
    risks: list[str]

# Provider ID comes from config — YAML, env, CLI flag, whatever.
provider_id = "claude"  # or bedrock / github-copilot / kimi / opencode / opencode-go / opencode-zen / openrouter

cls = runtime_for(provider_id)       # discovery lookup by ID
runtime = cls()                      # auth resolves from env / credential files
result = await runtime.execute(
    "Brief me on the project structure.",
    schema=Brief,
    model=ProviderModel(provider_id, "claude-haiku-4-5"),
)
print(result.structured)     # {"summary": "...", "risks": [...]}
print(result.cost.cost_usd)  # 0.0042
await runtime.close()

The same agent code now serves any installed adapter — swap provider_id (and model) in config, no import or instantiation changes. Add a new vendor to your project's YAML and ship.

Direct imports still work when you only ever need one adapter:

from airframe import ClaudeCodeRuntime
runtime = ClaudeCodeRuntime()

Use list_providers() to enumerate installed adapters at startup (handy for validating YAML config):

from airframe import list_providers
list_providers()  # ["claude", "github-copilot"]  — whichever extras are installed

The PyPI distribution name is airframe-agents. The import name is airframe.

Supported providers

Adapter PROVIDER_ID Vendor SDK Auth
BedrockRuntime bedrock aioboto3 boto3 chain (env / AWS_PROFILE / IAM role) + AWS_REGION
ClaudeCodeRuntime claude claude-agent-sdk Claude Max OAuth → ~/.claude/credentials.jsonANTHROPIC_API_KEY
CopilotRuntime github-copilot github-copilot-sdk GITHUB_TOKENgh auth
KimiRuntime kimi kimi-agent-sdk KIMI_API_KEY (Python 3.12+ only; mcp-version conflict with [claude])
OpenCodeGoRuntime opencode-go OpenAI compatible OPENCODE_API_KEY → opencode auth.json::opencode-go.key
OpenCodeServerRuntime opencode opencode-ai HTTP Basic (loopback unauthenticated; OPENCODE_SERVER_PASSWORD for remote)
OpenCodeZenRuntime opencode-zen OpenAI compatible OPENCODE_API_KEY → opencode auth.json::opencode.key
OpenRouterRuntime openrouter OpenAI compatible OPENROUTER_API_KEY

The OpenAI-compatible family (OpenCodeZenRuntime per-token, OpenCodeGoRuntime subscription, OpenRouterRuntime multi-vendor gateway today; Together / Groq / Fireworks as future siblings) shares the OpenAICompatibleRuntime base — subclasses are ~30 lines. See docs/adapters/third-party.md.

BedrockRuntime is the enterprise / managed-cloud path — AWS-billed access to a multi-vendor catalog (Anthropic, Meta, Mistral, Cohere, Amazon Nova) behind IAM-rooted auth and region pinning. Distinct from the OpenAI-compatible family because Bedrock speaks Converse, not Chat Completions.

Each adapter has one canonical provider ID. "anthropic" is reserved for a future direct-API AnthropicRuntime; "openai" for a future OpenAIRuntime. Current adapters cover the subscription paths (Claude Max, Copilot, ChatGPT Plus, opencode-go), the per-token gateways (OpenCode Zen, OpenRouter), the AWS-billed managed path (Bedrock), and the self-hosted agent server path (OpenCode Server — wraps opencode serve and fronts whichever upstream providers opencode auth login has configured, including ChatGPT-OAuth subscriptions).

ClaudeCodeRuntime is the only adapter that accepts Claude bindings. CopilotRuntime declines them — Claude served via Copilot Chat Completions emits markdown-fenced JSON instead of honouring tool calls, so it can't satisfy the structured-output contract.

Capability matrix

Current snapshot (run uv run python examples/probe_supports.py for the live version):

Feature Bedrock Claude Copilot Kimi OpenAI-compat OpenCode
STRUCTURED_OUTPUT_JSON_SCHEMA ✗ (SDK gap)
STREAMING / CANCEL
SESSION_RESUME
REASONING_EFFORT ✓ (Anthropic) ✓ (bool) ✓ (per-upstream)
REASONING_BUDGET_TOKENS ✓ (Anthropic) ✓ (Anthropic upstream)
VISION_INPUT
FILE_INPUT ✓ (Anthropic)
TOOLS_FUNCTION ✗ (SDK gap)
TOOLS_MCP_STDIO / _HTTP ✗ (SDK gap)
TOOLS_MCP_SSE ✗ (SDK gap)
PERMISSION_CALLBACK ✗ (SDK gap)
LIFECYCLE_HOOKS ✓ (6 kinds) ✓ (8 kinds) ✓ (7 kinds) ✓ (7 kinds) ✓ (6 kinds) ✓ (6 kinds)
BUDGET_USD_CAP
BUDGET_TURN_CAP

Capability flags are statically declared per adapter. Check runtime.supports(Feature.X) before invoking a feature; declined capabilities raise UnsupportedFeatureError with a feature= attribute so the call fails fast.

Full per-feature semantics in docs/capabilities.md; per-adapter quirks under docs/adapters/.

Why?

Each vendor ships a Python SDK that does something subtly different: the Claude Agent SDK exposes a subprocess + JSON-RPC interface; GitHub's Copilot SDK exposes a session + tool registration model; Moonshot's Kimi Agent SDK spawns the kimi-cli subprocess and streams typed WireMessage events; AWS Bedrock's aioboto3 client fronts a multi-vendor model catalog behind the Converse envelope and IAM auth; sst/opencode's opencode-ai SDK fronts a model- agnostic HTTP agent server (opencode serve) routing through any upstream opencode auth login has configured; the OpenAI-compatible gateways (OpenCode Zen, OpenCode Go, OpenRouter) speak Chat Completions HTTP. Each has its own auth chain, error taxonomy, cost-reporting shape, structured-output mechanism, and models-endpoint shape. Airframe collapses those differences behind one execute / session / reset / close / validate_binding / list_models / supports / unwrap interface, classifies every vendor's failures into a single hierarchy, and produces a single CostRecord / ModelInfo shape regardless of the vendor.

The protocol is intentionally narrow. The eight methods are the contract; everything else (auth chains, session caching, tool-call forcing, JSON-schema mode, envelope unwrapping, per-model metadata joining) lives inside each adapter, where vendor-specific behaviour belongs.

Anything above the protocol — retry policy, fallback across vendors, conversation memory, multi-agent orchestration — is left to the consumer. Airframe is the adapter layer; the application composes its own behaviour on top.

The shape — one narrow protocol plus pluggable vendor adapters, discovered by ID — is borrowed from JDBC, with the same goal: let the application code stay vendor-agnostic while each adapter absorbs its vendor's quirks.

Install

pip install airframe-agents[bedrock]        # BedrockRuntime
pip install airframe-agents[claude]         # ClaudeCodeRuntime
pip install airframe-agents[copilot]        # CopilotRuntime
pip install airframe-agents[kimi]           # KimiRuntime (Python 3.12+, separate venv — see note below)
pip install airframe-agents[opencode]       # OpenCodeServerRuntime (local opencode serve)
pip install airframe-agents[openai-compat]  # OpenCodeGoRuntime + OpenCodeZenRuntime + OpenRouterRuntime (+ future siblings)
pip install airframe-agents[all]            # Everything except [kimi] (mcp-version conflict)
pip install airframe-agents[testing]        # Conformance contract suite (pytest)

[kimi] co-installation note. kimi-agent-sdk 0.0.5 → kimi-cli 1.12 → fastmcp 2.12.5 → mcp<1.17, but claude-agent-sdk 0.2 requires mcp>=1.23. The two SDKs cannot co-install in one environment until upstream resolves; airframe declares the conflict in [tool.uv.conflicts] and excludes [kimi] from [all]. Users wanting both extras must split into separate venvs.

list_providers() filters by which extras you installed: airframe-agents[copilot] makes list_providers() return ["github-copilot"]. Pass installed_only=False to see every built-in provider for documentation UIs.

Sessions, streaming, and the new kwargs

runtime.execute(...) is convenient single-turn sugar. The full surface lives on runtime.session(...):

from airframe import (
    ClaudeCodeRuntime, FunctionTool, McpServerRef,
    PermissionCallback, PermissionDecision, PermissionRequest,
    HookEvent, ClaudeOptions, TextDelta, TurnComplete,
)
from pydantic import BaseModel

class AddArgs(BaseModel):
    a: float
    b: float

async def add(args: AddArgs) -> float:
    return args.a + args.b

class ApproveAll(PermissionCallback):
    async def handle(self, req: PermissionRequest) -> PermissionDecision:
        return "allow"

def log_event(e: HookEvent) -> None:
    print(f"[{e.kind}] {e.payload}")

runtime = ClaudeCodeRuntime()
sess = runtime.session(
    system="You are a careful math assistant.",
    tools=[FunctionTool(name="add", description="Add two numbers.",
                        params=AddArgs, handler=add)],
    mcp_servers=[McpServerRef(name="docs", transport="http",
                              url="https://mcp.example.com",
                              auth_token="...")],
    on_permission=ApproveAll(),
    on_event=log_event,
    provider_options=ClaudeOptions(strict_mcp_config=True),
)
try:
    async for event in sess.stream("What is 17 + 25?",
                                    max_turns=10, max_budget_usd=0.05):
        if isinstance(event, TextDelta):
            print(event.text, end="", flush=True)
        elif isinstance(event, TurnComplete):
            print(f"\nfinal cost: ${event.result.cost.cost_usd}")
finally:
    await sess.close()

session.stream() yields a discriminated union of five event variants: TextDelta, ReasoningDelta, ToolCallStart, ToolCallResult, TurnComplete. The variant set is shape-locked.

Per-kwarg semantics live in docs/capabilities.md; per-adapter quirks in each docs/adapters/ page.

Errors

Adapters classify vendor failures into a small hierarchy:

Error Meaning
RuntimeAuthError Credentials bad / expired / missing
RuntimeModelNotFoundError Server doesn't serve that model on this binding
RuntimeTransientError 5xx, rate limit, brief outage — recoverable
RuntimeStructuredOutputError Transport OK; payload didn't match schema
RuntimeBudgetExceededError max_turns= / max_budget_usd= cap tripped
UnsupportedFeatureError Capability declined (carries feature= attr)

Full list and the rest of the hierarchy in docs/reference.md#errors.

Escape hatch: unwrap()

When the portable surface doesn't expose a vendor-specific knob, reach the native SDK object via unwrap():

from claude_agent_sdk import ClaudeSDKClient
sess = runtime.session()
await sess.execute("hi")
client: ClaudeSDKClient = sess.unwrap(ClaudeSDKClient)
await client.interrupt()

Each adapter declares the native types it accepts; unsupported types raise TypeError. Runtime-level types via runtime.unwrap(...); session-level vendor objects via session.unwrap(...).

Live probes

examples/probe_*.py exercise each adapter end-to-end against a real CLI / HTTP endpoint. They're runnable demos, not part of make test. Auth issues surface as classified Runtime*Error.

uv run python examples/probe_supports.py        # capability matrix
uv run python examples/probe_streaming.py       # stream() against any installed adapter
uv run python examples/probe_tools.py           # FunctionTool round-trip
uv run python examples/probe_mcp.py             # external MCP server
uv run python examples/probe_permission.py      # PermissionCallback
uv run python examples/probe_hooks.py           # HookEvent observation
uv run python examples/probe_budget.py          # max_turns / max_budget_usd

Full list with one-line descriptions in docs/cookbook.md.

Documentation

Development

uv sync --all-extras --group dev
make test          # full suite (incl. integration tests, which self-skip without creds)
make test-fast     # exclude `integration` marker
make lint          # ruff
make typecheck     # mypy
make ci            # lint + format + typecheck + test

Integration tests run automatically when credentials for an adapter are configured (see auth.md).

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

airframe_agents-0.8.0.tar.gz (703.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

airframe_agents-0.8.0-py3-none-any.whl (206.1 kB view details)

Uploaded Python 3

File details

Details for the file airframe_agents-0.8.0.tar.gz.

File metadata

  • Download URL: airframe_agents-0.8.0.tar.gz
  • Upload date:
  • Size: 703.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for airframe_agents-0.8.0.tar.gz
Algorithm Hash digest
SHA256 29e4ec7ba9f3d1ece6b96771e42c9ba4fba0623b474b1cd7acc1779bbcdd353f
MD5 d93f83a0414540e8b343df47f1bd208d
BLAKE2b-256 99747144a18067c49e40133a3e0173b14361510f0cb284a13adeaff36969c510

See more details on using hashes here.

Provenance

The following attestation bundles were made for airframe_agents-0.8.0.tar.gz:

Publisher: release.yml on get2knowio/airframe

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file airframe_agents-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: airframe_agents-0.8.0-py3-none-any.whl
  • Upload date:
  • Size: 206.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for airframe_agents-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4ee0594028b405315a1210fe4dda6f0381bfc5b94c7c217e771a1b072e08975a
MD5 9790387a0c64ac6f43c20c60423873ac
BLAKE2b-256 e490a0e94a867bb1f7d4da781e2e4c16c939e8df7aae291b9f20a9fb8d32d97d

See more details on using hashes here.

Provenance

The following attestation bundles were made for airframe_agents-0.8.0-py3-none-any.whl:

Publisher: release.yml on get2knowio/airframe

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page