Skip to main content

Vendor-neutral agent runtime: one protocol, pluggable adapters for Claude Code, GitHub Copilot, OpenAI Codex, and OpenCode Zen.

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, OpenAI Codex, GitHub Copilot, OpenCode Go, OpenCode Zen, or OpenRouter by changing a single config value.

Quickstart

pip install airframe-agents[claude]   # or [bedrock] / [codex] / [copilot] / [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 / codex / github-copilot / 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", "codex"]  — 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
CodexRuntime codex openai-codex-sdk OPENAI_API_KEY → opencode auth.json~/.codex/auth.json
CopilotRuntime github-copilot github-copilot-sdk GITHUB_TOKENgh auth
OpenCodeGoRuntime opencode-go OpenAI compatible OPENCODE_API_KEY → opencode auth.json::opencode-go.key
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), and the AWS-billed managed path (Bedrock).

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 Codex Copilot OpenAI-compat
STRUCTURED_OUTPUT_JSON_SCHEMA
STREAMING / CANCEL
SESSION_RESUME
REASONING_EFFORT ✓ (Anthropic)
REASONING_BUDGET_TOKENS ✓ (Anthropic)
VISION_INPUT
FILE_INPUT ✓ (Anthropic)
TOOLS_FUNCTION
TOOLS_MCP_STDIO / _HTTP
TOOLS_MCP_SSE
PERMISSION_CALLBACK ✓ (session-wide)
LIFECYCLE_HOOKS ✓ (6 kinds) ✓ (8 kinds) ✓ (6 kinds) ✓ (7 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; OpenAI's Codex SDK passes a JSON Schema flag to a CLI subprocess; AWS Bedrock's aioboto3 client fronts a multi-vendor model catalog behind the Converse envelope and IAM auth; 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[codex]          # CodexRuntime
pip install airframe-agents[copilot]        # CopilotRuntime
pip install airframe-agents[openai-compat]  # OpenCodeGoRuntime + OpenCodeZenRuntime + OpenRouterRuntime (+ future siblings)
pip install airframe-agents[all]            # Everything
pip install airframe-agents[testing]        # Conformance contract suite (pytest)

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.6.1.tar.gz (503.0 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.6.1-py3-none-any.whl (180.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: airframe_agents-0.6.1.tar.gz
  • Upload date:
  • Size: 503.0 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.6.1.tar.gz
Algorithm Hash digest
SHA256 9aee874eec9f742876df093ad73cf2c7eee5f5febc9469b4c8d1b7e1ba74809a
MD5 cc28f4b1ff81ee2141d0902aa1c29329
BLAKE2b-256 4a404ace327660447c91d4d983ce1960575178335544d7a4f02f23d123ae7f3d

See more details on using hashes here.

Provenance

The following attestation bundles were made for airframe_agents-0.6.1.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.6.1-py3-none-any.whl.

File metadata

  • Download URL: airframe_agents-0.6.1-py3-none-any.whl
  • Upload date:
  • Size: 180.0 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.6.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ab38e3f8cea484bc7b480a4367e9f459c495f0d981026cfa7d128970e066d7a3
MD5 f54061bd6cf33535d4c016cee0c6bb42
BLAKE2b-256 26deb27880b56375b720c7e6b8ab9ee3407b6ecaa2cb610abba5742b8d353885

See more details on using hashes here.

Provenance

The following attestation bundles were made for airframe_agents-0.6.1-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