Skip to content

API Reference

Everything importable from agentshim is supported API, and so is agentshim.testing.

agentshim

agentshim: run coding-agent CLIs and understand what they printed.

Everything importable from this module is supported API. The test doubles live in agentshim.testing.

AccountLimits dataclass

What one read of a provider's limits returned.

A read never raises for a CLI problem: it returns unavailable and no windows, so a caller keeps the last good reading and says why it is old. account is a digest (see :func:account_digest), None when the provider named no account. plan is the provider's own plan word (team, pro). usage_url is where a person sees these limits.

__post_init__()

Require windows to be unique, and absent when the read failed.

failed(provider, reason, detail='') classmethod

A read that produced nothing, for reason.

Agent

A provider plus how it runs: transport, permissions, clock, and recovery policy.

Agent("claude", permissions=..., approvals=...) reaches the provider through the one-shot transport; passing a Transport instance uses it as is. permissions and approvals have no defaults: what the agent may do is always the caller's decision.

profile property

What the provider can do.

transport property

The transport sessions open conversations on.

__init__(provider, *, model=None, executor=None, confinement=None, permissions, approvals, clock=None, ids=None, retry=None, event_handlers=(), env=None, log=None, check_timeout=15.0, transport=TransportKind.ONE_SHOT, catalog=DEFAULT_CATALOG)

Build the transport and bind the policy inputs.

With a provider name, the transport is chosen by transport: the OneShotTransport (one process per turn, the default for now) or the provider's long-lived TransportKind.STREAM transport, on executor (the local host by default). A confinement wraps that executor so every command runs inside it, and its env becomes the agent's environment, so env may not also be given. With a ready-made Transport none of executor, confinement, env applies and passing one, or a transport kind, is an error: they would be silently ignored.

probe(*, timeout=15.0)

Report this agent's provider readiness without running a turn.

The checks run on the executor and environment the agent's turns use, so a confinement or container is probed where the agent would run. No model is called. Only an agent built from a provider name can probe; one built from a ready-made Transport raises ValueError.

Constructing an Agent already requires the binary, so use probe_provider to find out whether a provider is installed at all.

session(cwd, *, mcp_servers=(), reasoning_effort=None, checkpoints=None, checkpoint_key=None, idle_release_after=None, resume_id=None, previous_usage=None, skill_scope=SkillScope.ALL, mcp_scope=McpScope.ALL, config_scope=ConfigScope.ALL, plugins=None, partial_text=False, disable_subagents=False, disable_self_scheduling=False, raw_sink=None, new_conversation_id=None)

Open a session working in cwd (a host path).

mcp_servers and reasoning_effort are fixed for the session. With checkpoints and checkpoint_key the session resumes the conversation saved there and saves it after every turn; resume_id (with previous_usage) names a conversation explicitly and wins over a checkpoint. idle_release_after seconds of inactivity release the live conversation before the next turn. The scope arguments are transitional, for the one-shot transport only.

The last five are for a TransportKind.STREAM transport and are refused by the one-shot one: partial_text streams TextDelta events, disable_subagents starts the provider without its own delegation tools, disable_self_scheduling starts it without the tools that schedule its own later turns, raw_sink receives every line crossing the provider's pipe, and new_conversation_id chooses the id of a new conversation where the provider allows it (see ConversationSpec).

AgentEventHandler

Bases: Protocol

Receives every event of a turn, in order, on the caller's thread.

on_event(event)

React to one event.

Called synchronously in the middle of a turn, so a slow handler slows the turn down. A handler that raises fails the turn it is watching.

AgentSession

A provider conversation. One turn at a time; cancel is thread-safe.

profile property

Capability description of the provider this session talks to.

Re-exported from the agent so a caller can ask what a turn will support without reaching into the session's private agent reference.

__init__(agent, *, cwd=None, timeout=None, session_id=None, previous_usage=None, skill_scope=SkillScope.ALL, mcp_scope=McpScope.ALL, config_scope=ConfigScope.ALL, plugins=None)

Bind a conversation to one agent, with per-turn defaults.

The session owns the provider's conversation id, which is what makes successive turns resume one another. cwd and timeout are defaults an individual TurnRequest may override. Passing session_id adopts a conversation the provider already has, so the first turn resumes rather than starts fresh. previous_usage is that conversation's last report, including raw cumulative totals, when it was run outside this session. skill_scope limits which skills every turn's CLI may discover; a scope the provider's profile.skill_scopes does not list raises ProviderCapabilityError here, before any turn runs. mcp_scope limits which MCP servers every turn's CLI may connect to (McpScope.SESSION: only those in TurnRequest.mcp_servers) and is checked against profile.mcp_scopes the same way. config_scope limits which of the user's own CLI configuration (settings, hooks, global instructions, memory) every turn loads, and is checked against profile.config_scopes; a provider with profile.config_home_files additionally needs its state root pointed at a home from prepare_config_home.

adopt(session_id, *, previous_usage=None)

Continue an existing provider conversation on the next turn.

previous_usage seeds an external conversation's last raw report. Reports from conversations already seen by this session are retained. Returns False when the provider cannot resume, or when a turn is in flight and switching conversations would race it.

cancel(grace_s=5.0)

Stop the running turn: terminate, then kill after grace_s.

A turn is in flight from the moment turn() is entered, but the process handle only appears once the executor has spawned the CLI. Cancelling in that window (installing MCP servers, building argv, spawning) records the request instead of dropping it, and the process is terminated as soon as its handle is published. Calling this while no turn is running does nothing and leaves the next turn alone.

forget()

Start the next turn as a fresh conversation.

Returns False, changing nothing, when a turn is in flight, which is the rule adopt already follows: the running turn is about to write the conversation id it was given, so dropping it mid-flight would either be undone a moment later or discard an id nobody else has.

turn(request)

Run one prompt and return everything it produced.

The session counts as busy from here, not from the moment the process exists, so adopt and cancel both see a turn that is still installing MCP servers or building argv.

AgentShimError

Bases: Exception

Base class for every error agentshim raises.

ApprovalDenied dataclass

The agent asked for permission or input and the approval policy refused.

kind names what was asked (a command, a file change, a question) in the provider's own words, and detail carries the request.

ApprovalPolicy

Bases: Enum

What a transport does when the agent asks for permission or user input.

A long unattended run has no human to answer, so a transport never waits for one: it either answers at once or ends the turn.

ArgvContext dataclass

Everything build_argv may use.

The prompt is deliberately absent: it always goes on stdin so an agent's own pkill -f cannot match the CLI by prompt text.

AssistantText dataclass

Assistant-facing message text.

item_id names the message and is the same for every event that concerns it (the TextDelta events that built it and this one that completes it). agent is None for the conversation's own agent, and otherwise names the subagent that wrote it.

AuthPlan dataclass

Exactly what to hand a CLI so it logs in with one allowed credential.

env and files are the whole delivery: a caller that also forwards any other of the profile's auth_env_vars or auth_files (settings files included, which can carry credentials of their own) defeats the plan. source names the chosen credential (a variable name or a home-relative path); withheld names, never values, the credentials and endpoint settings present on the host that the plan leaves out, for a log.

AuthPolicy dataclass

Which credentials a caller allows, and the endpoint to use.

endpoint is None for the vendor's own endpoint: a base URL left in the host environment is then withheld, not forwarded. A URL sends every request there.

__post_init__()

Reject an empty credential set and an endpoint that is not an http(s) URL.

AuthState

Bases: str, Enum

Whether the provider CLI is authenticated, as far as its own tooling can say.

UNKNOWN is distinct from FAILED: it means the CLI offers no cheap way to tell (or the check itself could not run), so a caller must not treat it as a problem.

AuthUnavailableError

Bases: AgentShimError

No credential the policy allows is present, or the policy cannot be met.

The message names what each allowed kind would need, and never a value.

CallbackCommandStreamSink

Sink backed by plain callables, for callers without a sink class.

__init__(*, on_stdout, on_stderr, on_started=None)

Wire the sink to plain callables; only the output ones are required.

started(handle)

Forward the handle, when the caller asked for one.

stderr(line)

Forward the line to on_stderr.

stdout(line)

Forward the line to on_stdout.

Checkpoint dataclass

A provider conversation a later process can resume.

usage is the conversation's most recent report, kept so a provider whose usage is cumulative can still tell the next turn's increment.

CheckpointStore

Bases: Protocol

Where a session keeps its checkpoint between processes.

clear(key)

Forget key; clearing a key that holds nothing is not an error.

load(key)

Return the checkpoint saved under key, or None.

save(key, checkpoint)

Replace whatever is saved under key.

ClaudeHook dataclass

One command Claude Code runs on a hook event.

Attributes:

Name Type Description
event str

The hook event, such as PreToolUse or PostToolUse.

command Sequence[str]

The hook's argv. Claude Code runs hooks through a shell, so agentshim quotes it with shlex.join: every element reaches the process literally, whatever it contains. Use an absolute executable path; the agent's PATH is not the caller's. Any sequence of strings is accepted and stored as a tuple.

matcher str | None

For tool events, the tool-name pattern the hook applies to, such as Bash or Edit|Write. None matches every tool.

timeout_s float | None

Seconds Claude Code allows the hook before giving up; None keeps the CLI default.

__post_init__()

Reject a hook Claude Code would refuse or run incorrectly.

ClaudeProvider

Claude Code. sandbox, hooks and tools are provider options, not portable ones.

sandbox_env property

Environment a sandboxed run needs; empty when no sandbox is set.

Merge this into CliAgent(env=...) when you enable the sandbox.

__init__(*, sandbox=None, hooks=(), tools=None)

Fix the sandbox, hooks and tool policy for every turn this provider runs.

sandbox=True takes the default config; None and False both mean unsandboxed. hooks are added to the settings Claude Code loads for the turn, after agentshim's own read-confinement hook. tools=None runs every turn with --dangerously-skip-permissions (all built-in tools, every call allowed); a ClaudeToolPolicy runs it with only the tools and allow rules the policy names, under --permission-mode dontAsk.

build_argv(ctx)

Build the print-mode argv; the prompt is not in it, it goes on stdin.

classify_exit(error, *, resumed)

A resumed turn that failed for no reason the stream named lost its conversation.

claude --resume gives no distinguishable exit code for a missing transcript, so a resumed turn whose failure the parser could not classify is treated as a dead conversation. A failure the stream did classify (an overload, a usage limit, a login problem) happened inside a conversation that resumed, so it keeps its own kind.

install_mcp(workspace, servers)

Write the servers into <workspace>/.mcp.json for the turn's lifetime.

new_parser(emit, *, expect_structured)

Return a parser for one run's stream-json output.

ClaudeStreamTransport

A Transport over a long-lived claude --input-format stream-json process.

Construction resolves the binary on executor and runs its health check once, like CliAgent, so a broken install fails here. Only NativeMode.BYPASS and NativeMode.WORKSPACE_WRITE without network are supported (see STREAM_PERMISSION_MODES). hooks are Claude Code hooks added to the settings of every process.

profile property

Claude Code's profile, with the permission modes this transport enforces.

__init__(*, executor=None, env=None, clock=None, ids=None, hooks=(), check_timeout=15.0, startup_timeout_s=60.0, interrupt_grace_s=10.0, close_grace_s=5.0, steer_grace_s=10.0, log=None)

Check the install of claude on executor (the local host by default).

startup_timeout_s bounds the wait for a new process to answer initialize; interrupt_grace_s how long a timed-out turn gets to wind down after its interrupt before the process is killed; close_grace_s each step of a shutdown (EOF, terminate, kill); steer_grace_s how long, after a result, the CLI has to start the turn for a message it queued before that message is reported SteerRejected and the turn ends.

open(spec)

Start the process, resuming spec.resume_id, and wait until it is ready.

Raises ProviderCapabilityError for a permission mode outside STREAM_PERMISSION_MODES or for network access, SessionResumeError when the conversation to resume is gone, and TurnFailedError (a CliExitError) when the process dies before it is ready.

ClaudeToolPolicy dataclass

The tools a Claude Code turn is given and may call.

Attributes:

Name Type Description
builtin Sequence[str] | None

The built-in tools present in the session (--tools), such as ("Read", "Grep"). () removes every built-in tool; None keeps the CLI's default set. A tool that is absent cannot be called, whatever allowed says.

allowed Sequence[str]

Permission rules (--allowedTools) for calls that run without asking: a tool name ("Read"), an MCP server ("mcp__github", all of its tools), one MCP tool ("mcp__github__get_issue") or a scoped rule ("Bash(git log:*)"). Every other call is denied.

When the turn asks for an output schema, the provider also allows Claude Code's StructuredOutput tool, which is how the answer is returned.

__post_init__()

Freeze both lists and reject entries the CLI would misread.

argv(*, structured_output)

The flags that replace --dangerously-skip-permissions for one turn.

Each allow rule is its own argument, so a rule containing a space ("Bash(git log:*)") reaches the CLI whole.

only(allowed) classmethod

No built-in tools; only the calls allowed names (MCP servers or tools) run.

CliAgent

A configured provider CLI: binary, model, environment, handlers.

Binary lookup and the health check run once, here, so a broken install fails at construction instead of halfway through the first turn.

__init__(provider, *, model=None, executor=None, env=None, event_handler=None, event_handlers=(), check_timeout=15.0, log=None, catalog=DEFAULT_CATALOG)

Configure a provider CLI and prove it is usable before any turn runs.

Construction does real work: the binary is resolved on PATH and the provider's health check is run, so a broken install is reported here instead of halfway through the first turn.

Parameters:

Name Type Description Default
provider str | Provider

A registered provider name, or a Provider instance for a provider that is not in the registry.

required
model str | None

Provider-specific model id, or None for the CLI default.

None
executor CommandExecutor | None

Where commands run; defaults to the local host.

None
env Mapping[str, str] | None

Replaces the environment entirely rather than extending it. The default is interactive_env(), not os.environ, because provider CLIs are usually installed by a shell rc file.

None
event_handler AgentEventHandler | None

Merged with event_handlers; both spellings exist and either may be omitted.

None
event_handlers Sequence[AgentEventHandler]

Merged with event_handler, and fanned out to in the order given.

()
check_timeout float

Seconds allowed for the one-off health check. It is not a budget for a turn.

15.0
log Callable[[str], None] | None

Sink for agentshim's own progress lines; dropped by default.

None
catalog ProviderCatalog

Provider names and implementations to use; defaults to the built-ins.

DEFAULT_CATALOG

derive(*, model, event_handler)

Return a copy that uses model and reports events to event_handler.

The binary lookup and health check are not repeated: the copy shares the executor, environment and resolved binary of this agent. That is what lets one checked install serve many conversations, each with its own event routing.

run(request, *, cwd=None, timeout=None, skill_scope=SkillScope.ALL, mcp_scope=McpScope.ALL, config_scope=ConfigScope.ALL, plugins=None)

Run one turn in a throwaway session.

start_session(*, cwd=None, timeout=None, session_id=None, previous_usage=None, skill_scope=SkillScope.ALL, mcp_scope=McpScope.ALL, config_scope=ConfigScope.ALL, plugins=None)

Open a conversation whose turns resume one another.

skill_scope, mcp_scope and config_scope are fixed for the conversation (see AgentSession). previous_usage seeds the baseline when adopting a cumulative-usage provider conversation after a restart; pass its most recent TurnResult.usage.

CliCheckError

Bases: AgentShimError

The provider binary was found but its health check failed.

__init__(binary_path, message)

Record the binary that was found but failed its health check.

binary_path is the resolved path rather than the name looked up on PATH, because the binary that answered may not be the one the caller expected to find.

CliExitError

Bases: TurnFailedError

The provider CLI exited nonzero.

__init__(argv, returncode, stdout='', stderr='', message=None, *, kind=FailureKind.OTHER, detail='')

Capture the whole outcome of a nonzero CLI exit.

Both streams are retained because a provider may report the real cause on either one, and argv is copied so the error stays accurate after the caller mutates its command list. kind is the provider's classification of the failure and detail the error text its stream reported; the generated summary carries detail because a CLI that reports its failure in the stream leaves stderr empty. Passing message replaces the generated summary.

CliNotFoundError

Bases: AgentShimError

The provider binary is not on PATH.

__init__(binary, message=None)

Record the binary name that the PATH lookup failed to resolve.

The name is kept as an attribute so a caller can build an install hint without re-parsing the message. Passing message replaces the generated text.

CliTimeoutError

Bases: TurnTimeoutError

The provider CLI did not finish within the turn timeout.

__init__(argv, timeout)

Record the command and the budget it overran.

timeout is the budget in seconds that was allowed, not the elapsed time, which is unknown once the process has been killed. partial starts empty and is filled in by the session with whatever the stream parser read before the budget ran out; it is the only way back to the session id of a turn that named its conversation and then timed out.

Clock

Bases: Protocol

Monotonic time and cancellable waiting.

monotonic()

Seconds on a clock that never goes backwards.

wait(seconds, stop=None)

Wait seconds, or until stop is set; return True if stopped early.

CodexAppServerTransport

A Transport over codex app-server, one process per conversation.

Construction resolves the binary on executor and runs its health check once, like CliAgent, so a broken install fails here. executor is the agent's own (possibly confined) executor: the process is started through its spawn, so a confinement wraps it like any other command.

The timeouts are in seconds on clock: startup_timeout bounds the handshake, close_timeout each step of shutting the process down, and interrupt_grace how long an interrupted or timed-out turn may take to wind down before its process is stopped.

profile property

What Codex can do over app-server.

__init__(*, executor=None, env=None, check_timeout=15.0, log=None, clock=None, ids=None, pricing=None, startup_timeout=DEFAULT_STARTUP_TIMEOUT_S, close_timeout=DEFAULT_CLOSE_TIMEOUT_S, interrupt_grace=DEFAULT_INTERRUPT_GRACE_S)

Check the install on executor (the local host by default).

ids is accepted so this class satisfies StreamTransportFactory; Codex's server allocates every thread and turn id, so it is unused.

open(spec)

Start the process and a thread, or resume spec.resume_id.

Raises:

Type Description
ProviderCapabilityError

spec asks for something this transport cannot do, or Codex did not apply the permissions asked for.

SessionResumeError

Codex has no thread spec.resume_id.

TurnFailedError

The process would not start or answer.

TurnTimeoutError

The handshake took longer than startup_timeout.

CodexProvider

Codex (codex exec --json). sandbox is a provider option.

__init__(*, sandbox=None)

Fix the sandbox for every turn this provider runs.

None, the default, bypasses Codex's sandbox and approvals, for a caller that isolates the whole process itself. A CodexSandboxConfig keeps the CLI's own sandbox on instead.

build_argv(ctx)

Build the codex exec command line for one turn.

codex exec resume does not fall back to stdin when the prompt positional is omitted: only the literal - sentinel makes it read from stdin, and it has to sit right after the thread id. Plain codex exec treats stdin as the default, so it needs no sentinel.

classify_exit(error, *, resumed)

Report a lost rollout as SessionResumeError.

Codex says so explicitly on stderr, so unlike Claude a resumed turn that failed for another reason keeps its generic exit error.

install_mcp(workspace, servers)

Render servers as --config mcp_servers.* flags.

Codex has no project-scoped config file: its loader reads MDM policy, system-managed config, ~/.codex/config.toml, and the session --config override layer. So the servers live in argv and nothing in the workspace is touched, which is why workspace is unused.

new_parser(emit, *, expect_structured)

Build a stream parser for one run.

CodexSandboxConfig dataclass

How Codex sandboxes the commands the model runs.

Attributes:

Name Type Description
mode SandboxMode

read-only lets commands read but not write anywhere and blocks the network. workspace-write also lets them write the working directory, writable_roots and (by default) the temp directories. danger-full-access removes the sandbox but, unlike the bypass flag, keeps Codex's approval policy in force.

writable_roots Sequence[str]

Extra absolute directories commands may write. workspace-write only. Any sequence of strings is accepted and stored as a tuple, so equal configs compare and hash equal.

network_access bool

Let commands open network sockets, including local ones such as the Docker socket. workspace-write only.

writable_tmp bool

Keep /tmp and $TMPDIR writable, which is Codex's own default. workspace-write only.

excluded_commands Sequence[str]

Commands that run outside the sandbox, each written as shell words, e.g. "sdo detector check". An exempt command runs with no confinement at all: it can write anywhere and reach any socket the user can, so exempt only commands you trust with whatever arguments the model adds. A command the model runs is exempt when its words start with one of these, so sdo detector check --all is exempt and sdo detector is not. Anything else on the same command line (&&, |, ;, $(...)) keeps the whole line sandboxed. read-only and workspace-write only. Rendered as Codex exec-policy rules, which must be installed in a dedicated CODEX_HOME with install_rules; see agentshim.providers.codex.rules.

__post_init__()

Reject a config Codex would reject or silently ignore.

CommandExecutor

Bases: Protocol

Controls binary lookup, validation, and streaming execution.

Implement this to run provider CLIs somewhere other than the local host, such as inside a container or over a remote shell. agentshim keeps argv construction, parsing, session state, and event handling.

check_binary(path, env, *, timeout)

Validate the executable, or raise CliCheckError.

find_binary(name, env)

Return the value to use as argv[0], or raise CliNotFoundError.

run(request, sink)

Run request, streaming into sink, or raise CliTimeoutError.

spawn(request)

Start a long-lived process the caller writes to and reads from.

CommandHandle

Bases: Protocol

Executor-neutral handle for a started command.

Held so a caller can stop the command from outside the timeout path.

kill()

Stop the command now, with no chance for it to clean up.

terminate()

Ask the command to stop, leaving it a chance to exit on its own.

CommandRequest dataclass

One command to run.

timeout is seconds of wall clock, or None for no limit.

CommandResult dataclass

A finished command. stdout/stderr repeat what the sink saw.

CommandStreamSink

Bases: Protocol

Receives lifecycle and line-oriented output from a running command.

Lines keep the trailing newline the stream provided, matching TextIO.readline. Every callback runs on the thread that called CommandExecutor.run.

started(handle)

Receive the handle of the started command, before any output.

stderr(line)

Receive one line of stderr.

stdout(line)

Receive one line of stdout.

CompositeEventHandler

Fan an event out to a fixed list of handlers, in order.

__init__(handlers)

Snapshot the handlers to fan out to.

The iterable is copied at construction, so mutating the sequence that was passed in does not change this handler; the copy is exposed as the public handlers list instead.

on_event(event)

Pass the event to each handler in turn.

There is no error isolation: a handler that raises stops the ones after it, matching the single-handler case where the turn fails.

ConfigFileInstallation

Undo record for one merged JSON config file.

Owns exactly one file for the lifetime of one turn. restore is idempotent so a caller can run it from a finally without tracking whether the install succeeded.

target property

The config file this installation merged into and will restore.

Read-only so a caller can log or inspect the file without being able to point the undo record at a different one.

__init__(target, backup)

Hold the undo record for one already-merged config file.

Built by install_config_file once the merged file is on disk. backup describes the file as it was before the merge, which is what restore replays, so it must not be rebuilt from the current file.

restore()

Take agentshim's entries back out of the config file.

Idempotent, so a caller can run it from a finally without tracking whether the install got that far. Edits the agent made to the file during the turn are preserved, and a file the agent deleted stays deleted: both are workspace changes cleanup has no right to undo.

Never raises. The unmerge reads the file the agent may have rewritten, so it can find something that is no longer JSON, or an object; that is a normal outcome of letting an agent loose in the workspace, and a cleanup that raised for it would destroy the turn's result or mask the error the turn failed with. Anything the unmerge cannot handle falls back to writing the bytes the file had before the turn, and the returned note says so.

ConfigScope

Bases: Enum

Which of the user's own CLI configuration a session loads.

ALL: whatever the CLI loads by default: the user's settings, hooks, global instructions (~/.claude/CLAUDE.md, $CODEX_HOME/AGENTS.md), notify commands, profiles and memory. PROJECT: none of that; the session sees the workspace's own instructions and settings plus what agentshim passes for the turn, so the user running agentshim does not change what the agent does. Credentials keep working. A provider that lists a ProviderProfile.config_home_files can only isolate through a dedicated home: prepare_config_home builds it and returns the environment that points the CLI at it. A provider declares the scopes it can enforce in ProviderProfile.config_scopes; asking for another one is an error, never a silent ALL.

Confinement

Bases: Protocol

A boundary around the processes agentshim starts.

env property

Environment the confined process needs, which confine supplies.

The values travel in the launcher's environment (the host process that runs the confinement's client), not in its argv, so secrets do not appear in the host process table. wrap references them by name.

agent_path(host_path)

Return the path inside the confinement for host_path.

reap()

Kill every process this kind of confinement started for agentshim.

Includes processes left by an earlier host process. Idempotent; having nothing to kill is not an error.

wrap(argv, cwd)

Return the host argv that runs argv inside the confinement.

cwd is the directory as the agent sees it (see agent_path). The original argv is kept verbatim as the tail of the result.

ConnectionOptions dataclass

Dependencies for a requested connection mode.

ConsoleEventHandler

Render events as human-readable lines on a text stream.

Console output is an event handler rather than something the session does so a caller can replace it or compose it with handlers of their own.

__init__(stream=None, *, prefix='[agent]', color=True, show_lifecycle=False)

Set up a console renderer bound to one text stream.

stream is resolved once, at construction, so a later reassignment of sys.stdout does not redirect an existing handler. color controls ANSI escapes only, and show_lifecycle opts in to provider plumbing that is hidden by default because it is not model output.

on_event(event)

Render one event, then flush so a piped console stays live.

Dispatch is by event family. Events with no console rendering, such as SessionStarted and UsageReport, fall through and only flush.

ContextualStreamParser

Bases: Protocol

Optional parser extension, leaving existing provider factories compatible.

configure(context)

Set invocation context once, before any stream lines are fed.

Continuity

Bases: Enum

How a turn related to the conversation the caller expected it to run in.

ContinuityError

Bases: AgentShimError

A turn that required an unbroken conversation could not have one.

Raised by Session.prepare_turn when the caller expected to continue a conversation the session no longer holds.

__init__(expected, actual)

Record the conversation the caller expected and the one the session holds.

Conversation

Bases: Protocol

One provider conversation. One turn at a time.

conversation_id property

The provider's id for this conversation, once it has named one.

None before the first turn of a conversation that was not resumed.

close()

Release the connection. Idempotent.

interrupt()

End the running turn and keep the conversation. Thread-safe.

Does nothing when no turn is running.

turn(request, emit)

Run one turn, calling emit for each event on the calling thread.

Raises TurnFailedError (with its kind), SessionResumeError when the conversation it was asked to continue is gone, TurnTimeoutError, or another AgentShimError. Never retries. A turn ended by interrupt returns a result with interrupted set instead of raising.

ConversationSpec dataclass

Everything fixed for the life of one conversation.

permissions and approvals have no default: what the agent may do is always a decision of the caller. resume_id names a provider conversation to continue, and previous_usage is that conversation's last report, the baseline for a provider whose usage is cumulative.

The three scopes are transitional: they exist for the one-shot transport and go away when it does.

CopilotProvider

GitHub Copilot CLI.

build_argv(ctx)

Build the non-interactive argv for one turn.

The prompt is not in it: copilot reads the prompt from stdin when stdin is not a terminal, which is what the session gives it, so the -p flag is not used. --stream off keeps the CLI to whole assistant.message frames, and the three --allow-all-* flags are what makes a non-interactive run answer its own permission prompts.

classify_exit(error, *, resumed)

Return the exit error unchanged.

copilot --resume reports a missing session the same way it reports any other failure, and the id is not recoverable from the exit alone, so there is nothing to add to the error.

install_mcp(workspace, servers)

Render the servers into one --additional-mcp-config flag.

Copilot takes MCP servers as a JSON string on the command line, so nothing is written to the workspace and nothing needs restoring.

new_parser(emit, *, expect_structured)

Return a parser for one run.

Copilot has no output-schema mode, so the parser records the flag and never acts on it.

CostBreakdown dataclass

What a set of tokens costs, split by the class of token.

Attributes:

Name Type Description
input_usd float

Uncached input.

cache_read_usd float

Cache reads.

cache_write_usd float

Cache writes of either TTL.

output_usd float

Output, reasoning included.

total_usd property

All four classes together.

CostSource

Bases: str, Enum

Where a turn's cost came from, or that it has none to come from.

Every parsed turn names one: a reader that finds none is reading a usage built before the field existed, never a provider that left it out.

CredentialKind

Bases: str, Enum

What a credential bills and how it lives.

API_KEY is a static secret read from the environment or a key file: a vendor API key, or a token a proxy or gateway issued. SUBSCRIPTION is a personal or team plan login (Claude.ai, ChatGPT, a Google or GitHub account), usually an OAuth token the CLI refreshes.

DockerExecConfinement

Run processes with docker exec in a running container.

Parameters:

Name Type Description Default
container_id Callable[[], str]

Read on every wrap/reap, because the container may be replaced during a long run.

required
runner CommandExecutor

Executor used to run docker for reap.

required
path_map Mapping[str, str] | None

Host directory to container directory. agent_path maps by longest matching prefix on whole path components.

None
env Mapping[str, str]

Environment for the confined process. Each key is passed as -e KEY (name only) and the value rides in the docker client's environment (confine merges env into the launch environment), so values never appear in the host process table. A key with an empty name or =/NUL in it, or the reserved marker name, is rejected.

required
user str | None

Passed as -u when set.

None
docker str

The docker client binary.

'docker'

env property

The environment the confined process gets (names only on the command line).

__init__(container_id, *, runner, env, path_map=None, user=None, docker='docker')

Validate and freeze the configuration.

agent_path(host_path)

Map host_path through the longest matching path_map prefix.

Unmapped paths are returned normalized but otherwise unchanged.

reap()

Kill every marked process in the container.

A container that is gone or stopped is nothing to reap. Any other failure (no docker client, a daemon error, a timeout, a container missing sh/tr/grep) raises ReapError: the agents may still be running.

wrap(argv, cwd)

Build docker exec -i ... <container> <argv>; cwd is mapped.

EndpointSpec dataclass

How a CLI is pointed at an API endpoint other than its vendor's.

base_url_env takes the endpoint URL. companion_env are variables that only make sense with a custom endpoint (extra headers a gateway needs); they are forwarded from the host only when the policy names an endpoint. credentials are the kinds the CLI sends to a custom endpoint.

EnvCredential dataclass

A credential the CLI reads from one environment variable.

EventHandlerBase

No-op handler; subclass and override on_event.

on_event(event)

Ignore the event.

ExternalCredential dataclass

A way the CLI logs in that no plan can hand to another copy of it.

A cloud provider selected by a variable (whose own credentials live in other tools' state), a helper command named in a settings file, a credential store outside the CLI's state root. It is declared at its place in the precedence order so host_auth_plan knows when the host CLI would use it; plan_auth never chooses it. It is present when any variable in env is set, any home-relative path in paths exists, or any of the profile's settings files has one of settings_keys at its top level.

__post_init__()

Require some way to detect the credential.

FailureKind

Bases: str, Enum

Why a provider turn failed, as far as the provider let agentshim tell.

The provider package decides the kind from what its CLI reported (a structured error field, an HTTP status, or its own error text), so a caller can choose a policy without knowing how any CLI phrases its failures.

FileCredential dataclass

A credential the CLI reads from a home-relative file.

The file's kind is either fixed (kind) or read from its contents (classify, which returns None for contents it does not recognise); exactly one is set. The path is also one of the profile's credential_files.

__post_init__()

Require exactly one of kind and classify.

kind_of(contents)

The kind of credential contents hold, or None if unrecognised.

FilePosition dataclass

Where a read of one file stopped, and what is needed to continue it.

The caller stores this as it is and passes it back; only the fields outside state mean anything to it.

Attributes:

Name Type Description
size int

The file's size when the read began.

mtime_ns int

Its modification time then, in nanoseconds.

offset int

Bytes of complete lines already read.

guard_length int

How many bytes before offset the guard covers.

guard_hash int

FNV-1a (32-bit) of those bytes.

state bytes

The format's parser state at offset, opaque to the caller.

complete bool

Whether the read reached the end of the file. False when the byte budget stopped it; the next call continues from offset.

__post_init__()

Reject a position that could not have come from a real read.

FileScan dataclass

What one read found in one file.

Attributes:

Name Type Description
path Path

The file.

position FilePosition

Where to continue from next time.

records tuple[TranscriptRecord, ...]

Requests found after the known position (or in the whole file when reset). Each is final: it will not be reported again.

tail_records tuple[TranscriptRecord, ...]

Requests in an unterminated last line, which a writer may still be completing. Provisional: the next read reports them again in records once the line ends, so a caller shows them but does not keep them.

reset bool

The file was rewritten, truncated or its guard changed, so the known position could not be used and the whole file was read again. The caller drops every record it holds for this path.

malformed int

How many lines the format could not use, over the whole file so far (it replaces the caller's count, it is not a delta). Counts lines in tail_records' line too, if it is unusable.

FlagsInstallation dataclass

Installation for a provider that takes MCP servers as CLI flags.

restore()

Flags live only in one argv; nothing outlives the process.

FrameDirection

Bases: Enum

Which way a raw frame went, from agentshim's side of the pipe.

GeminiProvider

Gemini CLI in non-interactive stream-json mode.

build_argv(ctx)

Build the argv for one turn. The prompt is never part of it.

classify_exit(error, *, resumed)

Any nonzero exit on a resumed turn means the conversation is gone.

Gemini CLI reports a refused resume the same way it reports any other startup failure, so the cause cannot be told apart. A resumed turn that failed is unusable either way: the caller has to start a fresh conversation, and SessionResumeError is what tells it so.

install_mcp(workspace, servers)

Merge servers into <workspace>/.gemini/settings.json.

new_parser(emit, *, expect_structured)

Build the stream parser for one run.

HostAuthGap

Bases: str, Enum

Why host_auth_plan cannot reproduce how the host CLI logs in.

UNCLASSIFIED: the profile declares no credential sources. NO_CREDENTIAL: none of the declared sources is present, so the CLI either cannot log in or does it some way agentshim does not know. EXTERNAL_CREDENTIAL: the CLI would use an ExternalCredential. SETTINGS_ENV: a settings file sets a credential or endpoint variable of its own, so the environment the CLI logs in with is not the caller's.

HostAuthUnplannableError

Bases: AgentShimError

The host CLI's login cannot be reproduced by a plan; reason says why.

The message names the variables or files involved, never a value. A caller that still wants the CLI logged in elsewhere has only the host's whole auth state (auth_env_vars and auth_files) to hand over.

__init__(reason, message)

Record reason beside the message.

HostCommandExecutor

Default executor: one process per command, in its own session.

stdin is written from a helper thread so a prompt larger than the pipe buffer cannot deadlock against a child that is still printing. stdout and stderr are read on helper threads into one queue that the calling thread drains, so every sink callback is serialized on the caller's thread.

check_binary(path, env, *, timeout)

Run <path> --help and report any failure as CliCheckError.

find_binary(name, env)

Look name up on the turn's PATH, falling back to this process's.

Raises CliNotFoundError when neither lookup finds the binary.

run(request, sink)

Run the command, streaming into sink until both streams close.

The process is killed on every exit path, including a sink that raised or a timeout, so no CLI outlives the call.

spawn(request)

Start a long-lived process in its own session and return its handle.

Reader threads feed one queue; HostProcess.next_output drains it on the caller's thread, so the threads are an implementation detail of this real-I/O shell and never call back into the caller.

Each of private_files is a pipe the process inherits, named by its /dev/fd path. A pipe holds its content only until it is read, on no disk, and a child of the process that inherits it finds it drained.

HostProcess

A local process started by HostCommandExecutor.spawn.

Output order: lines in the order the reader threads queued them (stdout and stderr interleave as the OS delivered them), then one ProcessExited once both streams hit EOF and the process has exited. A reader failure ends that stream as if at EOF.

__init__(process)

Start the reader threads for process.

close_stdin()

Close stdin; later calls do nothing.

kill()

Send SIGKILL to the process group.

next_output(timeout)

Return the next item, or None after timeout seconds of nothing.

terminate()

Send SIGTERM to the process group.

wait(timeout)

Return the exit code, or None if still running after timeout.

write(data)

Write data to stdin and flush, or raise ProcessClosedError.

Blocks while the child is not reading and the pipe is full.

HttpMcpServer dataclass

MCP server reached over streamable HTTP or the older SSE transport.

transport is what the server actually speaks, and it has to be said rather than guessed: the two are different wire protocols, and a provider told the wrong one connects and then fails. It defaults to "http", which is what a current MCP server serves; the CLIs that cannot tell the two apart ignore it.

__post_init__()

Reject a spec the provider could never reach.

Validated here, at construction, because the same mistake would otherwise surface a turn later as an opaque provider-side MCP error.

IdAllocator

Bases: Protocol

Hands out identifiers that are unique within one run.

new_id(prefix)

Return a fresh identifier that starts with prefix.

IdleConversation

Bases: Conversation, Protocol

A conversation that reports what happens while no turn runs.

A provider process can do things between turns: finish a background task and start a turn of its own, report a rate limit, exit. turn reads the pipe only while a turn is running, so those would be held until the next turn (or lost). poll_idle reads them now.

poll_idle(emit)

Deliver what the provider has already said, without waiting; thread-safe.

Does nothing and returns True while a turn is running (the turn is reading). Returns False when the provider process has ended, so the conversation can no longer be used without being reopened by its id.

InMemoryCheckpointStore

A CheckpointStore that lives and dies with the process.

__init__()

Start empty.

clear(key)

Forget key.

load(key)

Return the checkpoint saved under key, or None.

save(key, checkpoint)

Replace whatever is saved under key.

Lifecycle dataclass

Provider plumbing that is not model output.

LimitWindow dataclass

One limit of an account: a share of an allowance and when it refills.

id is stable for the same window across reads and across the two ways a provider reports it, so readings merge by it. used_fraction is the share used, 0.0 to 1.0 (it may exceed 1 when the provider reports over-use). resets_at is when the window refills, timezone aware, or None when the provider did not say. window_minutes is the window's length when the provider says it, or when the window's name fixes it.

remaining_fraction property

The share still available, never below zero.

__post_init__()

Reject a window nobody could draw or merge.

LimitsReader

Bases: Protocol

A provider's way of reading its account limits. Provider knowledge lives here.

read(session)

Read the limits now, or say why they could not be read. Never raises for the CLI.

spend_reset_credit(session, *, idempotency_key, credit_id)

Spend one reset credit: credit_id, or the provider's next one when None.

idempotency_key names one logical attempt: spending twice with the same key spends one credit. Raises ProviderCapabilityError when the provider has no reset credits, CliNotFoundError when its CLI is not installed, and TurnFailedError or TurnTimeoutError when the call did not settle.

window(status)

The window a turn's RateLimitStatus describes, in the shape a read returns.

LimitsSession dataclass

Where and how one limits read runs: the same inputs a readiness probe takes.

timeout bounds the whole read, from starting the CLI to its answer. clock measures it, so a simulation controls it.

LimitsSource

Bases: Protocol

Optional: a plugin whose CLI can say what its account's plan limits are.

A plugin without it, or answering None, has no readable limits.

limits_reader()

This provider's limits reader, or None if it has none.

LimitsUnavailable dataclass

A read that produced no limits, and why.

LimitsUnavailableReason

Bases: str, Enum

Why a read has no windows.

UNSUPPORTED: this provider has no way to read limits. API_KEY: the CLI is billed by token, so there is no plan to have limits. PROBE_FAILED: the read could not be done now (no binary, not logged in, timed out, an error reply); LimitsUnavailable.detail says which.

LoginSpec dataclass

How a person signs a CLI in, as the CLI itself does it.

argv follows the binary and starts the CLI's own subscription login (a browser page, or a URL to open). device_argv is the variant for a machine without a usable browser, where the CLI prints a code to enter on another device, or None when the CLI has none. needs_terminal is true when the command prompts the person (a menu, a pasted code), so it must run attached to a terminal; false when it runs unattended and only prints a URL. Whether a CLI is signed in is not here: the provider's ProbeSpec.auth reads that, where the CLI has a status command.

__post_init__()

Reject an empty command.

command(binary, *, device=False)

The full command line, with binary first.

Raises:

Type Description
ValueError

device is asked for and the CLI has no device login.

McpConfigError

Bases: AgentShimError

An MCP config file is unreadable, unwritable, or is not a JSON object.

McpInstallation

Bases: Protocol

The undo record for MCP servers installed for one turn.

restore()

Put the workspace back the way the turn found it.

Must be idempotent, must never raise, and must be safe to call from a finally block, including when the install itself failed part way through: cleanup that raises there would destroy the turn's result or mask the error the turn actually failed with. Returns a note when the workspace could not be restored the normal way, for the caller to log, and None otherwise.

McpMechanism

Bases: Enum

How a provider is told about MCP servers.

McpScope

Bases: Enum

Which MCP servers a session's CLI may connect to.

ALL: whatever the CLI finds by default, including the user's and the project's own MCP configuration, plugin servers and account-level connectors. SESSION: only the servers the session was given through TurnRequest.mcp_servers; a session given none sees no MCP server at all. A provider declares the scopes it can enforce in ProviderProfile.mcp_scopes; asking for another one is an error, never a silent ALL.

McpServerState

Bases: str, Enum

Whether a session's tool server is usable, as far as the provider could say.

McpServerStatus dataclass

One tool server of a session, by the name the caller gave it.

ModelPricing dataclass

One model's USD price per million tokens, with where and when it was read.

Attributes:

Name Type Description
vendor str

Who bills for the model ("openai", "anthropic").

model str

The model id as the vendor names it.

input_usd float

Base (uncached) input.

cache_read_usd float

Input read from the prompt cache.

cache_write_usd float

Input written to the prompt cache (Anthropic's five-minute TTL). A vendor with no cache-write surcharge lists its base input rate here.

output_usd float

Output, reasoning included.

source str

URL of the official pricing page the numbers were read from.

checked date

Date the numbers were last checked against source.

cache_write_1h_usd float | None

One-hour-TTL cache writes; None when the vendor has no such tier (cache_write_usd applies).

reasoning_usd float | None

Reasoning output, when billed apart from output; None means reasoning is billed as output.

estimated bool

Whether any number is an estimate rather than a published price; note must then state the basis.

note str

Caveats, such as a long-context surcharge the table leaves out.

key property

The (vendor, model) this entry prices.

__post_init__()

Reject a missing source or date, and any price that is not a finite non-negative number.

from_dict(data) classmethod

Build an entry from a mapping; checked is an ISO date string or a date.

Raises:

Type Description
(ValueError, TypeError)

A required field is missing or invalid.

relative_weights()

Weights as multiples of the base input rate (uncached input is 1.0).

to_dict()

A JSON-serializable mapping that :meth:from_dict reads back.

usd_weights()

Weights in USD per million tokens, for :meth:TokenUsage.weighted_total.

ModelSpec dataclass

One model's context window, with where and when it was read.

Attributes:

Name Type Description
vendor str

Who serves the model ("openai", "anthropic").

model str

The model id as the vendor names it.

context_window int

The model's context window in tokens (input and output together), at the vendor's default for that id.

source str

URL of the official page the number was read from.

checked date

Date the number was last checked against source.

display_name str | None

A readable name for the model ("Opus 5.5"), or None for a caller-built entry that has none.

family str | None

The model line the model belongs to ("Claude", "GPT", "Gemini"); set together with display_name.

tier int | None

The model's rank within its family, 1 the smallest and cheapest; versions of one tier share a rank. None for a model that sits on no tier ladder.

tier_name str | None

The tier's readable name ("Opus"); set together with tier.

generation_id str | None

A short stable token naming the model's generation within its vendor ("5.5"); every model of one generation shares it, and no two generations of a vendor do.

generation str | None

The generation as people call it ("Claude 5.5", "GPT-6"); set together with generation_id.

generation_rank int | None

The generation's rank among its vendor's, 1 the newest; set together with generation_id. ModelTable checks that one vendor's generations have distinct ranks.

key property

The (vendor, model) this entry describes.

__post_init__()

Reject a missing name, source or date, and a window that is not a positive int.

ModelTable dataclass

A versioned set of :class:ModelSpec entries.

Attributes:

Name Type Description
version str

Names this set of facts; change it whenever a fact changes.

last_updated date

Date the table as a whole was last updated. No entry may have been checked later.

entries tuple[ModelSpec, ...]

One entry per (vendor, model).

__post_init__()

Reject a missing version, duplicate keys, and entries checked after last_updated.

get(vendor, model)

The entry for exactly (vendor, model), or None.

with_entries(entries, *, version, last_updated=None)

A new table with entries added, replacing any with the same key.

ModelUsage dataclass

One model's share of a turn's usage.

A turn can run several models: Claude's Task subagents and advisors run on their own, and ProviderUsage.tokens leaves them out. The sum of :attr:tokens over :attr:ProviderUsage.by_model is the whole turn.

Attributes:

Name Type Description
model str

The model id as the provider reports it.

tokens TokenUsage

This turn's tokens for this model. Only the primary model carries the turn's turns count.

recorded_usd float | None

The pinned-table price of tokens at the standard tier; None when the table has no price for the model. This is the figure a turn's recorded cost sums, never the CLI's own. It is not a bill: the pinned table has no speed tiers, so a row whose speed is fast or ultrafast was billed more than this. For the price at the tier, use :func:agentshim.price_tokens with the entry's rates at speed, or add :func:agentshim.speed_premium_usd.

speed UsageSpeed | None

The service speed the turn ran at, on the primary model only; None when the provider did not report one (read as standard).

primary bool

Whether this is the model the turn was run with. Exactly one row of a turn is.

__post_init__()

Reject an empty model or a cost that is not a finite, non-negative number.

to_dict()

A JSON-serializable mapping of this row.

NativeMode

Bases: Enum

Which sandbox the agent applies to the commands it runs.

NativePermissions dataclass

The native sandbox a session asks of its agent.

BYPASS disables the agent's own sandbox and approvals. It is only safe when an outside Confinement bounds the agent; on its own it gives the agent the full authority of the user running it.

writable_roots (absolute paths) and network widen WORKSPACE_WRITE and are rejected with any other mode, so a value is never silently ignored.

__post_init__()

Reject a combination the agent would ignore or misread.

bypass() classmethod

No native sandbox. Only safe inside an outside Confinement.

read_only() classmethod

Commands may read but not write.

workspace_write(writable_roots=(), *, network=False) classmethod

Commands may write the working directory and writable_roots.

NoRunningTurnError

Bases: SessionStateError

steer was called when no turn could take a message.

Either no turn is running, or the running one has not started yet or is already finishing. agentshim never holds a steer back for a later turn: a caller that wants one delivered later sends it with the next prompt.

NoopInstallation

Installation for a turn that asked for no MCP servers.

restore()

Nothing was installed.

NullEventHandler

Bases: EventHandlerBase

Handler that intentionally drops every event.

NullSink

Sink that drops everything, for runs whose output is not parsed.

started(handle)

Ignore the handle.

stderr(line)

Ignore the line.

stdout(line)

Ignore the line.

OneShotTransport

A Transport over per-turn CLI processes.

Construction resolves the binary and runs the provider's health check once, like CliAgent, so a broken install fails here and not mid-run. Only NativeMode.BYPASS is supported, which is how the one-shot path has always run.

profile property

What the provider can do.

__init__(provider, *, executor=None, env=None, check_timeout=15.0, log=None, catalog=DEFAULT_CATALOG)

Check the install of provider on executor (the local host by default).

open(spec)

Start a conversation, resuming spec.resume_id on its first turn.

Raises ProviderCapabilityError for a permission mode other than bypass, or for reasoning effort or MCP servers the provider cannot take.

OpencodeProvider

opencode's non-interactive run mode with JSON events.

build_argv(ctx)

Build the argv for one turn. The prompt is never part of it.

classify_exit(error, *, resumed)

Any nonzero exit on a resumed turn means the conversation is gone.

opencode run --session on an unknown id fails the same way as any other run, so the cause cannot be told apart. A resumed turn that failed is unusable either way: the caller has to start a fresh conversation, and SessionResumeError is what tells it so.

install_mcp(workspace, servers)

Merge servers into <workspace>/opencode.json.

new_parser(emit, *, expect_structured)

Build the stream parser for one run.

OutputSchema dataclass

A JSON Schema the provider should force its final answer to satisfy.

Two directories are carried because a container-executed CLI resolves paths inside the container: the schema file is written to host_dir but named to the CLI as cli_dir. Providers that take the schema inline (Claude Code) use only schema.

OutputSchemaStyle

Bases: Enum

How a provider accepts a JSON Schema for structured output.

ParsedTurn dataclass

What a stream parser recovered from one run.

ParserContext dataclass

Optional invocation context for parsers that need cumulative usage.

PluginInstallError

Bases: AgentShimError, ValueError

A session's plugins could not be installed, so no agent was started.

Also a ValueError, which is what the failure was before it had a type.

PluginRef dataclass

One named plugin from a local marketplace checkout.

Remote sources are deliberately absent: the caller supplies the checkout whose marketplace manifest is to be installed by the provider CLI.

__post_init__()

Reject ambiguous paths and plugin selectors.

PolicyConfig dataclass

The fixed inputs of a policy: what the provider and the caller allow.

PricingContext dataclass

Conditions under which a rate is requested; omitted values mean standard tier.

PricingTable dataclass

A versioned set of :class:ModelPricing entries.

Attributes:

Name Type Description
version str

Names this set of prices; change it whenever a price changes.

last_updated date

Date the table as a whole was last updated. No entry may have been checked later.

entries tuple[ModelPricing, ...]

One entry per (vendor, model).

__post_init__()

Reject a missing version or date, duplicate keys, and entries checked after last_updated.

from_dict(data) classmethod

Build a table from a mapping such as a parsed JSON or TOML file.

Raises:

Type Description
(ValueError, TypeError)

version, last_updated or an entry field is missing or invalid.

get(vendor, model)

The entry for exactly (vendor, model), or None.

to_dict()

A JSON-serializable mapping that :meth:from_dict reads back.

with_entries(entries, *, version, last_updated=None)

A new table with entries added, replacing any with the same key.

A changed table is a different set of prices, so it needs its own version. last_updated defaults to the latest of this table's date and the new entries' checked dates.

Process

Bases: Protocol

A started process, owned by whoever spawned it.

close_stdin()

Close stdin (end of input). Idempotent.

kill()

Stop the process (and its group) now. Idempotent.

next_output(timeout)

Return the next output item, or None if timeout seconds pass with none.

timeout=None blocks until there is an item. Items come in one deterministic order. After ProcessExited has been returned, every further call returns ProcessExited again.

terminate()

Ask the process (and its group) to stop. Idempotent.

wait(timeout)

Return the exit code, or None if it has not exited within timeout.

write(data)

Write data to stdin.

Raises ProcessClosedError when stdin is closed or the process is gone.

ProcessClosedError

Bases: AgentShimError

A long-lived process can no longer take input: stdin is closed or it exited.

ProcessExited dataclass

The process ended. Always the last item a process yields.

Every line the process wrote before exiting is delivered first. returncode is negative when a signal ended it.

Provider

Bases: Protocol

Argv construction, stream parsing, and provider-specific options.

build_argv(ctx)

Build the command line for one turn, argv[0] included.

The prompt is never part of argv; the session always writes it to stdin, so nothing that inspects the process table can read it.

classify_exit(error, *, resumed)

Narrow a nonzero exit to the most specific error this CLI can justify.

resumed says the turn passed a session id. CLIs rarely give a distinct exit code for a conversation that no longer exists, so this flag is usually the only evidence that resuming, not the prompt, failed.

install_mcp(workspace, servers)

Make servers reachable for one turn and return the undo record.

workspace is None when the turn runs without a cwd, which a provider that installs servers by writing a config file has no way to satisfy and should reject.

new_parser(emit, *, expect_structured)

Create a parser for one run, wired to publish events through emit.

A parser is single-use. expect_structured says the caller asked for an output schema, which is what lets a parser tell a missing structured result apart from a turn that never wanted one. Parsers that implement ContextualStreamParser receive a ParserContext before streaming. Existing parsers need not implement that optional extension.

ProviderCapabilityError

Bases: AgentShimError

The request asked for something the provider cannot do.

ProviderCatalog

An immutable, explicitly extended set of provider plugins.

__init__(plugins=())

Build a catalog; duplicate names fail rather than override another plugin.

names()

List providers in stable order.

plugin(name)

Find one plugin or report the names available in this catalog.

probe_spec(name)

Return the legacy static probe declaration, if this plugin has one.

provider(name)

Construct a one-shot provider by name.

stream_names()

List providers with a long-lived transport.

stream_transport(name, *, executor, env, clock, ids, check_timeout, log)

Construct a provider's long-lived transport.

test_support(name)

Return optional test adapters for a provider.

with_plugin(plugin)

Return a new catalog containing plugin, preserving this one.

ProviderError dataclass

The provider reported an error.

ProviderPlugin

Bases: Protocol

Required methods for every provider; unsupported facts return None.

name property

Stable catalog key.

connect(options)

Create the requested one-shot provider or stream transport.

connection_modes()

Declare which connection modes connect accepts.

describe()

Describe capabilities independently of local installation state.

known_models()

Enumerate facts available for a reproducible table snapshot.

known_prices()

Enumerate standard-tier prices for a reproducible table snapshot.

model_info(model, table=None)

Resolve context-window facts; table overrides built-in facts.

pricing_info(model, context, table=None)

Resolve a rate for model and context, or report it unknown.

probe(executor, env, timeout)

Check readiness on the supplied executor, without running a model.

test_support()

Return consumer-test adapters, if provided.

ProviderProfile dataclass

Everything a caller needs to know about a provider without running it.

The last four fields are additive (0.6.1+) and all default, so an existing keyword-built ProviderProfile keeps working unchanged. container_env is environment a CLI needs to run as root in a container, beyond auth (e.g. Claude Code's IS_SANDBOX=1). state_root_env is the documented variable that relocates state_dirs[0], or None when the CLI names none. auth_files are the home-relative paths, each inside a state_dirs entry, whose contents are the minimal set to copy so the CLI is logged in elsewhere; credential_files (0.15.3+, default empty) is the subset of them that holds refreshable login credentials rather than settings; resume_state_paths (0.15.7+, default empty) names the conversation history a resume reads back. mcp_config_file is the workspace-relative path a CONFIG_FILE provider writes its MCP config to for a turn, and None for CLI_FLAGS/NONE providers.

default_model property

The model to run when a caller must name one and has none: models[0], or None.

supports_plugins property

Whether this transport can isolate and load SessionPlugins.

ProviderStatus dataclass

The readiness of one provider CLI on one executor.

version is None when the binary is missing or did not report one. auth_detail explains auth in words fit for a log or an error message, including the fix when authentication failed.

ProviderTestSupport dataclass

Optional provider-owned adapters for the generic test helpers.

ProviderUsage dataclass

Normalized token counts plus the raw provider mapping.

raw keeps the last usage mapping the CLI printed so a caller can diagnose a normalization gap without re-parsing the stream. increment_known=False means the invocation's token increment could not be reconstructed (for example, a Codex resume without a baseline). Its token counts are zero placeholders, with turns still counted; callers must check this marker before pricing or budgeting the counts.

__post_init__()

Say NOT_REPORTED for a usage with no cost and no stated source.

to_dict()

Flatten the normalized counts, cost, and provider name into one mapping.

raw is deliberately left out: it is provider-shaped and unbounded, while this mapping is meant to be cheap to log on every turn.

RandomIds

<prefix>-<uuid4 hex>: unique across processes and runs.

new_id(prefix)

Return prefix joined to a random UUID.

RateCatalog

Bases: Protocol

Optional: a plugin that ships rates, with speed tiers, for the models it prices.

A plugin without it ships none. These are the rates of the pinned table shipped in the wheel, which :func:agentshim.rates.bundled_rates collects.

known_rates()

Rates for the models this provider prices.

RateDocumentError

Bases: AgentShimError

A rate document is not JSON, or holds no model with usable rates.

RateEndpoint dataclass

One place to fetch the document from.

Attributes:

Name Type Description
url str

The address.

accept str

The Accept header to send, since GitHub's contents API returns the raw file only when asked for application/vnd.github.raw+json.

RateEntry dataclass

One model's rates at each speed it has a price for.

Attributes:

Name Type Description
name str

The normalized flat name (see :func:normalize_name).

standard Rates

The standard tier.

fast Rates | None

The fast tier, when the document lists one.

ultrafast Rates | None

The ultrafast tier, when the document lists one.

__post_init__()

Reject a name that is not already normalized.

at(speed)

The rates at speed, or None when the document lists none for it.

Never falls back to another tier: whether a request at an unlisted speed is billed as standard is the caller's decision.

RateFetchError

Bases: AgentShimError

A :class:RateSource could not fetch a document.

RateLimitStatus dataclass

The provider's view of one rate-limit window, as it just reported it.

Optional: a provider that has no such signal emits nothing, and a field the provider did not state is None, never zero. One event describes one window, so a provider that reports several (a five-hour and a weekly one) emits several events. A consumer reads the newest event per (limit, window).

window names the window in the provider's words (five_hour, primary). limit names the limit it belongs to when the provider has more than one (Codex limit_id). used_fraction is the share of the window already used, 0.0 to 1.0 (it may exceed 1 when the provider reports over-use). resets_at is epoch seconds. exhausted is True when the provider says this limit is reached, False when it says it is not, None when it does not say. raw is the provider's own mapping, for diagnostics.

remaining_fraction property

The share of the window still available, or None when usage is unknown.

RateNaming

Bases: Protocol

Optional: a plugin that knows which flat names a model may be filed under.

A provider not implementing it gets :func:agentshim.core.rates.candidate_names.

rate_names(model)

Candidate flat names for model, most specific first; none if it has no price.

RateRead dataclass

Rates and how current they are.

Attributes:

Name Type Description
table RateTable

The rates to price with: the stored document laid over the pinned table, or the pinned table alone. Every model the pinned table prices is priced in it.

status RateStatus

How current the document behind table is.

fetched_at datetime | None

When the document behind table was fetched; None for the pinned table.

known_models int

How many names table knows, the pinned table's included.

refresh_due bool

Whether :func:refresh_rates would try to fetch now.

RateSnapshot dataclass

What a :class:RateStore keeps between runs.

Attributes:

Name Type Description
document bytes | None

The last document that parsed, as fetched; None before the first success.

fetched_at datetime | None

When document was fetched; set exactly when document is.

last_attempt_at datetime | None

When a fetch was last tried, whether it worked or not.

__post_init__()

Check the times are timezone-aware and tell one consistent story.

RateSource

Bases: Protocol

Fetches a rate document. The caller supplies the HTTP client.

fetch(endpoint)

The body at endpoint.

Raises:

Type Description
RateFetchError

The request failed or timed out. Any other exception is a bug in the source and propagates.

RateStatus

Bases: str, Enum

Where the rates in a :class:RateRead came from.

RateStore

Bases: Protocol

Persists one :class:RateSnapshot. The caller chooses where.

read()

The stored snapshot, or None when nothing was stored.

write(snapshot)

Replace the stored snapshot.

RateTable dataclass

Rates by flat model name, read on one day.

Attributes:

Name Type Description
checked_on date

The day the document these rates came from was read.

entries Mapping[str, RateEntry]

Each entry under its own normalized name. A bare alias of a vendor-prefixed name is an entry too, when its sources agree.

__len__()

How many names this table knows.

__post_init__()

Freeze the entries and check each is filed under its own name.

lookup(name)

The entry for name, ignoring case and surrounding space, or None.

Rates dataclass

USD per million tokens for one model at one speed.

A free model has zero rates, which is why this type allows them and :class:~agentshim.core.pricing.ModelPricing does not.

Attributes:

Name Type Description
input_usd float

Uncached input.

output_usd float

Output, reasoning included.

cache_read_usd float

Input read from the prompt cache.

cache_write_usd float

Input written to the prompt cache.

cache_write_1h_usd float | None

One-hour-TTL cache writes; None when the document lists no such rate (cache_write_usd then applies).

__post_init__()

Reject a rate that is not a finite, non-negative number.

scaled(multiple)

These rates times multiple, such as a fast tier priced at twice standard.

RawFrame dataclass

One line exchanged with a provider process, exactly as it crossed the pipe.

text has no trailing newline. Frames reach ConversationSpec.raw_sink in the order agentshim wrote or read them, which is enough to replay a conversation against a scripted peer.

RawOutput dataclass

One stdout line that was not a provider event.

ReapError

Bases: AgentShimError

A confinement could not confirm that its processes were killed.

Raised instead of returning quietly: a reap that failed leaves agents running against the workspace. A container that no longer exists is not an error, because nothing in it can still run.

Reasoning dataclass

Thinking or reasoning text; item_id and agent as on AssistantText.

RenewalBudget dataclass

When a long conversation should be retired and restarted cold.

A conversation that grows without bound gets slower and more expensive, so a provider can declare limits. They are checked after a successful turn: reaching max_turns successful turns in the conversation, or any one turn reaching max_turn_input_tokens input tokens or max_turn_duration_ms milliseconds (a "heavy turn"), retires it. None leaves a limit off.

ResetCredit dataclass

One credit that refills the account's limits at once when spent.

id is the provider's opaque handle for it, the value :func:~agentshim.limits.use_reset_credit takes to spend this credit and no other.

__post_init__()

Require an id, and aware times.

ResetCreditResult dataclass

What spending a reset credit did, and the limits read after it.

ResetCredits dataclass

The reset credits an account holds.

available is the count the provider states. credits lists the ones it described, which can be fewer (the provider may cap the list, or skip the lookup): never infer the count from its length.

__post_init__()

Reject a negative count.

ResetOutcome

Bases: str, Enum

How the provider answered a request to spend a reset credit.

RetryPolicy dataclass

How long to wait before each retry of a transient failure.

delays has one entry per retry, so len(delays) retries follow the first attempt. An empty tuple never retries.

__post_init__()

Freeze the delays and reject a negative or non-finite one.

RunFinished dataclass

The CLI process exited.

RunStarted dataclass

The CLI process is about to start.

SandboxConfig dataclass

A subset of the sandbox key in Claude Code's settings.json.

Attributes:

Name Type Description
fail_if_unavailable bool

Exit with an error when the sandbox cannot start instead of silently running unsandboxed.

auto_allow_bash bool

Auto-approve sandboxed bash commands; they are already constrained by the sandbox.

allow_unsandboxed_commands bool

Whether the dangerouslyDisableSandbox escape hatch stays available.

excluded_commands list[str]

Commands that should run outside the sandbox.

allow_write list[str]

Extra writable paths (cwd is always writable).

deny_write list[str]

Paths sandboxed commands must not write.

allow_read list[str]

Paths to re-allow inside a deny_read region.

deny_read list[str]

Paths sandboxed commands must not read.

allowed_domains list[str]

Outbound domains allowed for bash subprocesses. Does not affect Claude's own API calls.

extra_settings dict[str, Any]

Raw keys merged into the sandbox block.

confine_native_reads_to list[str]

Roots outside which Claude's own Read/Glob/Grep/Edit/Write tools are denied. The OS sandbox only wraps bash, so this is enforced by a PreToolUse hook. Empty means Claude's native tools may read anywhere.

SchemaDialect

Bases: Enum

Which JSON Schema subset a provider accepts.

STRICT is Codex's --output-schema subset, which is OpenAI's strict structured outputs: every object declares properties, sets additionalProperties: false and lists every property in required (optional values are made nullable instead), the root is not an anyOf, and every $ref resolves. OPEN also accepts optional properties and a schema-valued, true or absent additionalProperties.

SchemaDialectError

Bases: ProviderCapabilityError

The output schema uses constructs the provider's dialect rejects.

__init__(problems)

Collect every dialect problem found in one schema.

All problems are reported together so a caller repairing a schema does not have to re-run the check once per offending keyword.

Session

One logical conversation, kept alive by a recovery policy.

Normally made by Agent.session. One turn at a time: prepare_turn then run. interrupt and steer may be called from any thread; everything else belongs to the thread driving the session.

conversation_id property

The conversation the next turn continues, or None for a cold start.

last_turn_conversation_id property

The conversation the last completed turn ran in; a retirement keeps it.

profile property

What the provider behind this session can do (steering, resuming, ...).

__enter__()

Return the session; it closes on exit.

__exit__(*exc_info)

Close the session.

__init__(transport, spec, *, clock, ids, policy, event_handlers=(), checkpoints=None, checkpoint_key=None, previous_usage=None, agent_path=None, host_cwd=None, log=None)

Bind a session to its collaborators and resume what it can.

spec is the conversation template: its resume_id (with previous_usage) names a conversation to continue, and otherwise the checkpoint saved under checkpoint_key does. policy configures the recovery policy. When the agent is confined, agent_path maps host paths to the agent's view and host_cwd is the working directory on the host (spec.cwd is the agent's view of it). Passing a store without a key, or the reverse, is an error rather than a session that silently never checkpoints.

adopt(conversation_id, previous_usage=None)

Continue a provider conversation from the next turn on.

Returns False, changing nothing, when the session already holds a conversation with newer history, a turn is prepared or running, or the provider cannot resume.

close()

Interrupt any running turn and release everything. Idempotent.

interrupt(ticket=None)

End the running turn, keeping the conversation. Thread-safe.

Also ends a retry wait. With no turn running it does nothing and leaves the next turn alone.

With ticket, interrupts exactly that turn and never another: a turn prepared and not yet run is cancelled the moment it would start (its run raises TurnCancelledError and the provider is never asked); a running turn is interrupted as without a ticket; a turn that already ended, or that a later prepare_turn superseded, is left alone, so a late interrupt cannot land on the turn after it.

poll_idle(on_event=None)

Deliver what the provider did since the last call, outside any turn.

Events go to the agent's handlers and to on_event, as in run. Call it regularly while no turn runs: a turn reads the provider itself, and anything the provider says between turns is otherwise held until the next one. Returns False when the provider's process has ended or the session is closed (the next turn reopens the conversation by its id), True otherwise, also with nothing to read, while a turn runs, or when the provider reports nothing between turns (IdleConversation). Thread-safe.

prepare_turn(request, *, expect_conversation=None, pin=False)

Reserve a turn and name it, without running it.

expect_conversation demands that this turn continue exactly that conversation: if the session holds another, or none, this raises ContinuityError and the turn is never run. pin keeps the conversation through renewal and never replaces a refused resume with a fresh one. A later prepare_turn supersedes this ticket.

release()

Close the live conversation but keep its id, so the next turn resumes it.

report_tools(timeout_s=30.0)

Say whether the session's tool servers connected, before any turn.

Returns one McpServerStatus per tool server the session was given (PENDING for one the provider had not settled within timeout_s), an empty tuple for a session given none, and None when the provider cannot say before a turn or no conversation is open (call warm first). A caller that must not run an agent without its tools checks for FAILED and treats None and PENDING as unknown. Thread-safe.

run(ticket, *, on_event=None)

Run the turn ticket names, to completion.

ticket must be the most recent one prepared and not yet run, or this raises SessionStateError. Events go to the agent's handlers and to on_event. Errors are the transport's: a failure the policy could not recover from is re-raised unchanged.

steer(text, ticket=None)

Send text into the turn that is running now. Thread-safe.

With ticket, the message goes only to that turn: if it is not the turn running now (it ended, or has not started) this raises NoRunningTurnError, so a late steer cannot land in the turn after it.

The provider folds the message into the running turn instead of waiting for the next one. It does not queue: the message goes out now or this raises. Raises ProviderCapabilityError when the provider cannot take a message mid-turn (profile.supports_steer is false), and NoRunningTurnError when no turn is running, the conversation is between attempts (a retry wait), or the provider's turn has not started or is finishing. Whether and when the provider took the message is reported as SteerDelivered, SteerConsumed and SteerRejected events to the turn's handlers.

warm()

Open the conversation now, before any turn, and return the id the provider gave it.

Returns the id of the conversation that is already live when there is one. A provider that names a conversation only when its first turn runs gives None (unless the conversation spec chose the id). Raises what Transport.open raises. The first turn then finds the conversation open and runs in it. Does nothing, returning None, on a closed session or while a turn is prepared or running.

SessionPlugins dataclass

Exactly these plugins, plus CLI built-ins, in a dedicated session home.

Passing this option excludes the operator's plugins, skills and config, regardless of the session's scopes. An empty refs selects no plugins. Omitting the option preserves the provider's normal scope behavior. home must be fresh on first use, belongs to the caller, and must be retained unchanged for resume. Never share it between sessions.

__post_init__()

Require a relocatable, immutable session specification.

SessionPolicy

The pure step function of a session. Holds only its configuration.

__init__(config=None)

Bind the configuration; the policy itself keeps no state.

initial_state()

A session that holds no conversation and has done nothing.

step(state, event)

Apply event to state.

Raises ValueError for an event that cannot happen in state (for example Opened with no turn running): the shell, not the caller of the library, is at fault.

SessionResumeError

Bases: CliExitError

A resumed turn failed because the provider conversation is gone.

__init__(argv, returncode, session_id, stdout='', stderr='', *, detail='')

Attach the conversation id that the provider refused to resume.

Raised instead of a plain CliExitError so a caller can tell a dead conversation apart from a failed turn and retry from scratch. Its kind is always OTHER: a provider that classified the failure as anything else has shown that the conversation is not what failed.

SessionStarted dataclass

The provider named the conversation.

SessionState dataclass

Everything the policy remembers. Immutable: step returns a new one.

SessionStateError

Bases: AgentShimError

A session or conversation was used in a state that cannot serve the call.

Covers a closed session or conversation, a second turn while one is running, and a turn ticket that is stale or already used.

SettingsSpec dataclass

The JSON settings files a CLI reads from its home when it starts.

files are home-relative paths. env_key names a top-level object whose string entries the CLI applies over its own environment (Claude Code's env), or None when the CLI has no such block. Settings can change which credential or endpoint the CLI uses, so host_auth_plan reads them; neither plan function delivers them.

SkillInvoked dataclass

The agent loaded a skill's instructions.

Emitted at the point in the stream where the load happened, directly after the ToolCall that performed it; tool_id names that call. source_path is the skill file or directory when the stream reveals it, and None otherwise.

SkillScope

Bases: Enum

Which skills a session's CLI may discover.

ALL: whatever the CLI finds by default, including the user's own skills and installed plugins. PROJECT: only the skills under the session's working directory (the profile's skill_dirs) plus the CLI's built-in ones, so the user running agentshim does not change what the agent is offered. A provider declares the scopes it can enforce in ProviderProfile.skill_scopes; asking for another one is an error, never a silent ALL.

SkillSignal

Bases: Enum

How much a provider's output stream says about skills.

NONE: nothing; a caller must treat skill use as unknown, never as zero. STRUCTURED: the CLI emits a dedicated, documented frame. INFERRED: agentshim derives it from tool activity (for example a shell read of a skill's SKILL.md), so a load by other means can be missed.

SkillSummary dataclass

Skills a turn (or several) offered and loaded.

None means unknown: the provider's stream carries no such signal, or did not carry it this time. It never means zero. An empty tuple means the signal was available and nothing matched.

invocation_count property

How many loads happened, or None when unknown.

invoked property

Distinct invoked skill names, in order of first use.

SkillTracker

Event handler that folds skill events into a SkillSummary.

Feed it one turn or many; the summary covers everything it has seen.

__init__(profile)

Track skills for a provider with profile's declared signals.

on_event(event)

Record a skill event; ignore every other event.

summary()

Return what has been seen so far.

SkillsDiscovered dataclass

The provider listed the skills it offers this session.

names are the skill names as the provider reports them (a plugin skill may carry a plugin: prefix). Only emitted by a provider whose profile declares skill_discovery other than SkillSignal.NONE, and only when its stream actually carried the list.

SpawnRequest dataclass

One long-lived process to start.

cwd of None means the executor's own working directory.

A process's command line is readable by every local user, so an argument that holds a secret goes in private_files: argv has a placeholder (a whole element) where the path of a file goes, and the value here is what the process reads from that file. Nothing is written to disk. HostCommandExecutor gives the process an inherited pipe. An executor that cannot, because its process is on the far side of a command it runs, uses inlined_argv.

inlined_argv()

argv with each placeholder replaced by the file's content, for the provider to parse.

StagedCredentialFile dataclass

A host credential file the CLI must see at home_path under its home.

refreshable files hold a login the CLI rewrites when it refreshes the token; share those writable so the refreshed token reaches the host.

Stderr dataclass

One stderr line.

StderrLine dataclass

One line of stderr, with the same newline convention as StdoutLine.

StdioMcpServer dataclass

MCP server launched as a subprocess over stdio.

startup_timeout_s bounds initialization and the first tool listing; tool_timeout_s bounds one tool call. Providers that cannot express a requested per-server timeout reject it rather than silently dropping it.

__post_init__()

Reject a spec the provider could never launch.

Validated here, at construction, because the same mistake would otherwise surface a turn later as an opaque provider-side MCP error.

StdoutLine dataclass

One line of stdout, with its trailing newline (as TextIO.readline).

The last line of a stream that ends without a newline has none.

SteerConsumed dataclass

The provider took a steered message into the running turn's context.

Emitted where the provider reports it: Codex when the message becomes an item of the turn, Claude Code when it echoes the message back. A message that the provider only queued for the next turn of its own (Claude Code, when the model is not at a tool boundary) is consumed when that turn starts; the steering turn then continues until that follow-on finishes.

SteerDelivered dataclass

A message sent with steer reached the provider, which accepted it.

This says the provider holds the message, not that the model has seen it (see SteerConsumed). Emitted on the thread running the turn.

SteerRejected dataclass

The provider refused a steered message; the turn is unaffected.

reason is the provider's own explanation. Codex refuses a steer for a turn that cannot take one (a compaction, say) or that has just ended.

SteerableConversation

Bases: Conversation, Protocol

A conversation that accepts a message while a turn runs.

A transport whose profile has supports_steer opens conversations of this kind. steer neither queues nor retries: the message goes to the running turn now or the call raises.

steer(text)

Send text into the running turn. Thread-safe.

Raises NoRunningTurnError when no turn is running or the running one cannot take a message yet (or any longer). Returns once the message is on its way; the outcome is reported as SteerDelivered, SteerConsumed and SteerRejected events, on the thread running the turn.

StopSignal

A cancellation flag that a wait can observe.

A thin wrapper over threading.Event so a Clock implementation, real or fake, has one small type to honor.

__init__()

Start unset.

is_set()

Whether set has been called.

set()

Mark the signal; every current and later wait returns early.

wait(timeout)

Block up to timeout seconds; return whether the signal is set.

StreamParser

Bases: Protocol

Turns one provider's stdout/stderr into events and a ParsedTurn.

feed_stderr(line)

Consume one stderr line.

Providers mix progress chatter and real failures on stderr, so which of the two a line is has to be decided per provider, here, rather than by the session.

feed_stdout(line)

Consume one stdout line, emitting whatever events it carried.

The line keeps the trailing newline the stream produced, and the call arrives on the thread that ran the turn, so a parser may hold plain unsynchronized state across lines.

finish()

Report what the run produced, after the last line has been fed.

Called once per run and before the exit code is inspected, so a parser must return its best partial reading of a failed run rather than raise.

SystemClock

The real clock.

monotonic()

Return time.monotonic().

wait(seconds, stop=None)

Sleep up to seconds; wake early and return True when stop is set.

TextDelta dataclass

The next fragment of a message, of reasoning or of a tool's output, as the provider streams it.

Emitted only when the conversation was opened with partial_text. Concatenating the deltas of one item_id in order gives a prefix of the text the later AssistantText or Reasoning with that item_id reports whole (a TOOL_OUTPUT one, of the output the ToolResult with that tool_id reports); a consumer that has the whole text can drop the deltas. item_id is None only when the provider gave the fragment no identity. agent is as on AssistantText.

TextKind

Bases: Enum

Which kind of text a TextDelta extends.

TokenUsage dataclass

Token counts for one turn, normalized across providers (see module docstring).

Invariants, checked on construction: cache_read_input_tokens + cache_write_input_tokens <= input_tokens, cache_write_1h_input_tokens <= cache_write_input_tokens and reasoning_output_tokens <= output_tokens.

cached_input_tokens is kept equal to cache_read_input_tokens: pass either one. Before 0.7.0 it also counted cache writes on Claude, opencode and Copilot; :meth:from_dict reads such records back.

uncached_input_tokens property

Input billed at the base rate: neither read from nor written to the cache.

__add__(other)

Accumulate a running total over the turns of a session.

Every field is summed, turns included, so folding per-turn usages together yields both the token totals and the turn count.

__post_init__()

Mirror the deprecated alias and check every invariant.

from_dict(data) classmethod

Read back a :meth:to_dict mapping, including one written before 0.7.0.

A mapping without cache_read_input_tokens predates the split: its cached_input_tokens counted cache writes too (Claude, opencode, Copilot; Codex reported none), so the writes are subtracted back out. Unknown keys are ignored; missing or non-numeric counts read as 0.

to_dict()

Flatten the counts into a JSON-serializable mapping keyed by field name.

Includes the derived uncached_input_tokens. Keys are only ever added, so a reader of an older mapping keeps working.

weighted_total(weights)

Sum every token class times its weight.

The classes partition the turn (uncached input, cache reads, default-TTL cache writes, one-hour cache writes, non-reasoning output, reasoning output), so each token is weighted exactly once.

TokenWeights dataclass

Caller-supplied weight per token class, for :meth:TokenUsage.weighted_total.

The classes partition a turn's tokens, so no token is weighted twice. cache_write_1h_input defaults to cache_write_input and reasoning_output to output (reasoning billed as output). The weights carry no unit: USD per token, per million tokens, or a multiple of the base input rate all work. agentshim's own prices live in :mod:agentshim.pricing.

__post_init__()

Reject a weight that is not a finite, non-negative number.

ToolCall dataclass

A tool was invoked.

tool_id is the item's stable id. agent is as on AssistantText.

ToolReportingConversation

Bases: Conversation, Protocol

A conversation that can say, before any turn, whether its tool servers connected.

report_tools(timeout_s)

Return one status per configured tool server.

Waits up to timeout_s for the provider to settle each; a server it has not settled by then is PENDING.

Does nothing and reports every server PENDING while a turn runs. Never raises for a server that failed: that is the answer.

ToolResult dataclass

A tool finished; tool_id is that of the ToolCall it completes.

ToolTracker

Pairs a provider's tool-call frames with its tool-result frames.

Result frames usually carry only the call id, so the tool name and the elapsed time have to be remembered from the call.

__init__(*, unknown_name='Tool')

Start a tracker with no calls recorded.

unknown_name is what name() reports for a result frame whose call was never seen, so a caller always has a displayable label even when the provider's frames do not pair up.

duration(tool_id)

Return seconds since the matching call, or None if unpaired.

name(tool_id)

Return the tool name recorded for tool_id.

start(tool_id, tool)

Record a tool call so a later result can be attributed to it.

TranscriptFormat dataclass

What a provider's transcript files look like.

Attributes:

Name Type Description
subdirs tuple[str, ...]

Directories under the provider's state root that hold transcripts.

markers tuple[bytes, ...]

ASCII byte strings. A line with none of them cannot carry anything the parser needs (usage, or state such as the model in force), so it is skipped without decoding it.

selected_fields Mapping[str, object]

The fields the parser reads, as nested mappings with True for a whole value. A line too large to decode whole is reduced to these before the parser sees it.

parser Callable[[Path, bytes | None], TranscriptParser | None]

Builds a parser for one file. Given the state of a previous read it continues from there; it returns None when it cannot read that state, and the file is then read again from the start.

suffix str

Which file names are transcripts.

unreadable_suffixes tuple[str, ...]

Transcript names agentshim cannot read (a compressed archive). They are reported in failed, not skipped.

session_file Callable[[Path], str] | None

The name every copy of one session's file has, for a provider that moves or copies a file between its directories after it was read. A file with the name of one already read is read on from there, so the move adds nothing twice. None when a file keeps its path.

__post_init__()

Reject a format that would read nothing, or read non-ASCII markers.

TranscriptParser

Bases: Protocol

Reads one file's lines in order and remembers what later lines depend on.

malformed property

How many lines so far were unusable.

feed(line)

The requests one line records. Unusable lines count in :attr:malformed.

reject()

Count one line the reader could not decode, so the parser never saw it.

snapshot()

The parser's state, for :meth:TranscriptFormat.parser to continue from.

TranscriptRecord dataclass

One model request's usage, or the part of it one line of a transcript added.

Attributes:

Name Type Description
timestamp datetime

When the request was recorded (timezone-aware). For the tokens a later line of the file added, when that line was written.

model str

The model id as the transcript names it.

session_id str

The session it belongs to; a subagent's records carry their parent's session.

cwd str | None

The working directory the session ran in: an absolute path on the user's machine, so a personal one. None unless the caller asked for it with include_cwd, and then only when the file says.

tokens TokenUsage

The request's tokens. input_tokens includes cache reads and writes, as everywhere in agentshim. A request is counted once, at the most any line of the file reported: the records of one request add up to its final usage, the first holding the request and its turns, and later ones (turns 0) only what a later line added.

speed UsageSpeed

The service speed it was billed at.

reported_usd float | None

A cost the file states. None for every shipped format: a CLI's own figure is not trusted (see :mod:agentshim.providers.claude.cost).

TranscriptRoot dataclass

A directory a provider keeps transcripts under.

Attributes:

Name Type Description
provider str

The provider that wrote them.

path Path

The directory, resolved from the environment (it may not exist).

TranscriptScan dataclass

The result of one bounded read of a root.

Attributes:

Name Type Description
files tuple[FileScan, ...]

The files read this call, in path order.

unchanged int

Files whose size and modification time match the known position of a finished read, and files that are earlier copies of a session already read further under another path, so nothing was read.

skipped int

Files too old to matter for the window.

pending int

Files not finished: the byte budget ran out first. Call again with the new positions until it is zero.

failed tuple[Path, ...]

Files and directories that could not be read, or that the format cannot read. Never treated as empty: the caller keeps what it knows and reports the source as incomplete.

found bool

Whether the root directory exists.

TranscriptSource

Bases: Protocol

Optional: a plugin whose CLI keeps session files agentshim can read for usage.

A plugin without it, or answering None, has no readable transcripts.

transcripts()

The format of this provider's session files, or None if it has none readable.

TransformingExecutor

Wrap an executor and rewrite each CommandRequest on the way in.

This is how a caller wraps argv in an OS sandbox or prefixes it with docker exec without agentshim knowing about either. The health check goes through run, so the transform applies to it too: a binary that only exists inside the container is still checkable.

__init__(inner, transform, *, find_binary=None)

Wrap inner, rewriting every request with transform.

Pass find_binary when the binary exists only on the far side of the transform, where a host-side lookup would fail or find the wrong one.

check_binary(path, env, *, timeout)

Run <path> --help through the transform, or raise CliCheckError.

find_binary(name, env)

Use the override when one was given, else the inner executor's lookup.

run(request, sink)

Run the rewritten request on the inner executor.

spawn(request)

Start the rewritten request on the inner executor.

The transform sees the spawn as a CommandRequest with no stdin and no timeout, and only the argv, cwd and env it returns are used. A descriptor does not follow a command across a docker exec or a wrapper, so private_files are inlined into its argv.

Transport

Bases: Protocol

A way to reach one provider, producing conversations.

profile property

What the provider behind this transport can do.

open(spec)

Start (or, with spec.resume_id, resume) a conversation.

Raises ProviderCapabilityError for a permission mode that is not in profile.native_permission_modes. A refused resume may surface here or at the first turn, always as SessionResumeError.

TransportKind

Bases: Enum

Which way an Agent reaches a provider by name.

Turn dataclass

A completed turn.

TurnCancelledError

Bases: AgentShimError

An interrupt arrived before the turn could start, so it never ran.

TurnFailedError

Bases: AgentShimError

A turn failed, and the provider (or transport) could say why.

kind is the classification a retry policy keys on, and detail the provider's own error text. Every transport raises this type or a subclass, so code above the transport catches it without knowing how the provider is reached.

__init__(message, *, kind=FailureKind.OTHER, detail='')

Record the classification and the provider's error text.

TurnInterrupted dataclass

The turn was interrupted before it finished; the conversation is kept.

TurnRequest dataclass

One prompt plus the per-turn overrides that shape how it is run.

mcp_workspace = None class-attribute instance-attribute

Host directory that receives config-file MCP installs.

Defaults to cwd. A container-executed turn has no host cwd (the CLI runs at the container path), so it names the bind-mounted host workspace here instead; the same split OutputSchema makes with host_dir and cli_dir.

TurnResult dataclass

Everything one turn produced.

TurnTicket dataclass

A turn that has been prepared but not yet run.

The id exists before dispatch so a caller can journal its intent to run the turn, and find it again after a crash.

TurnTimeoutError

Bases: AgentShimError

A turn did not finish within its time budget.

The conversation is not known to be damaged, so a session keeps it.

__init__(timeout, message=None)

Record the budget in seconds that was overrun.

UsageReport dataclass

Provider accounting for the turn so far.

UsageSpeed

Bases: str, Enum

The service speed a request was billed at.

WindowKind

Bases: str, Enum

What a window is, in words every provider shares.

account_digest(provider, identity)

An opaque, stable key for the account identity names on provider.

Equal inputs give equal digests on every machine, so a caller can pool windows of one account seen from several. It is a hash of a name, not a secret: it keeps an email or an account id out of what is stored and shown, and does not hide it from someone who already knows it.

bundled_rates(*, catalog=None)

The pinned table shipped in the wheel: every model a provider prices, with speed tiers.

It comes from the same generated data as the recorded-cost table, so a standard rate cannot differ between the two.

cache_savings_usd(tokens, rates)

What cache reads saved against paying the input rate for the same tokens.

compact_json(schema)

Serialize schema for an inline CLI flag, keeping argv small.

compose_event_handlers(handler, handlers=())

Collapse the two constructor spellings into one handler.

Returns a NullEventHandler when nothing was supplied so callers never branch on None.

confine(executor, confinement)

Return executor with every command run inside confinement.

run, spawn and the binary health check all go through confinement.wrap. The binary lives inside the confinement, not on the host PATH, so find_binary trusts the bare name. The host working directory is dropped (the launcher runs wherever the host executor runs); the agent's directory is passed to wrap instead. confinement.env is merged over the request environment, for the launcher to forward by name.

context_window(provider, model, table=None, *, catalog=None)

model's context window in tokens when run through provider, or None when not known.

cost_usd(usage, pricing)

USD cost of usage at pricing.

Every token class is priced once: uncached input, cache reads, cache writes by TTL, and output (reasoning at reasoning_usd when set).

default_models(*, catalog=None)

Compose model facts supplied by the selected provider catalog.

default_pricing(*, catalog=None)

Compose prices supplied by the selected provider catalog.

The shipped entries are generated from a dated LiteLLM snapshot, and every one was checked on that day, so the version names it (litellm-2026-10-30) and changes exactly when the snapshot, and with it any price, does.

dialect_problems(schema, dialect)

Return every reason schema is not expressible in dialect.

An empty list means the provider will accept it. The list is returned rather than raised so a caller can decide between failing and falling back to a prompt-level schema instruction. Each problem starts with the JSON pointer of the offending node.

STRICT is OpenAI's strict structured-output subset, which Codex sends with strict: true: every object declares properties, sets additionalProperties: false and lists exactly its properties in required (an optional value is expressed as nullable instead), the root is not an anyOf, and every $ref resolves inside the document. Real CLI probes on 2026-10-04 confirmed both dialects accept description, title and examples on schema nodes. STRICT rejects them beside $ref; OPEN accepts those siblings and oneOf variants.

get_provider(name, *, catalog=DEFAULT_CATALOG)

Construct a provider by name from catalog.

host_auth_plan(profile, *, env, home)

Say how profile's CLI logs in by itself on a host, as a plan a confined copy can use.

The CLI's own choice: the first present source in credential_sources, of any kind, at whatever endpoint env points it to. The plan delivers that credential, and the host's endpoint variables (EndpointSpec base_url_env and companion_env) exactly as they are set, so a confined copy given only the plan sends the same credential to the same place. AuthPlan.endpoint is the host's base URL, or None for the vendor's endpoint.

The host's base URL is reproduced, not validated: a value the CLI cannot use fails the copy the same way it fails the host CLI (Claude Code fails every request with "Invalid URL" and never falls back to its vendor). The credential's kind is not checked against EndpointSpec.credentials either: what the CLI does with a credential and a custom endpoint is the CLI's behaviour, and the copy reproduces it.

Raises:

Type Description
HostAuthUnplannableError

the profile declares no credential sources, none is present, the CLI would log in with an ExternalCredential, or a settings file sets a credential or endpoint variable; reason is the matching HostAuthGap.

install_config_file(target, *, server_key, servers, defaults=None)

Merge servers into the JSON config at target, returning the undo record.

defaults are top-level keys the provider needs ($schema and the like) that are only added when the file does not already set them.

A symlinked config is followed to the file it names, so the backup, the merged write and the eventual restore all address one inode. The returned installation therefore reports the resolved path.

interactive_env(*, refresh=False)

Return the environment an interactive login shell would provide.

Provider CLIs are usually installed by a shell rc file that a non-interactive process never sources, so PATH and credentials are read from bash -i. A successful capture is run once per process and cached: it costs hundreds of milliseconds and the answer does not change. Each of the two attempts (env -0, then env for a shell without it) has its own 10s, so a call takes at most 20 seconds. Any failure falls back to os.environ for that call only: a failed capture is never cached, so the next call asks the shell again instead of keeping a machine's PATH out of the answer for the life of the process.

limit_providers(*, catalog=DEFAULT_CATALOG)

The providers in catalog whose limits can be read, in name order.

limit_window(provider, status, *, catalog=DEFAULT_CATALOG)

The window a turn's RateLimitStatus describes, in the shape :func:read_limits returns.

None when the status names no window, states no share used and does not say the limit is reached, or when provider has no limits at all. An unknown provider raises ValueError.

map_mcp_servers(servers, agent_path)

Map the absolute paths in stdio MCP servers to where the agent sees them.

A relative command or argument (a flag, a workspace-relative path) is left alone, as is a server reached over HTTP.

materialize(schema, host_dir)

Write schema under host_dir as <sha>.json and return the path.

The name is content-addressed so repeated turns with the same schema reuse one file, and the write is atomic so a CLI reading the path concurrently never sees a partial document.

A schema carrying a value JSON cannot express, a NaN or an infinity, raises ProviderCapabilityError: the provider cannot be given this schema, which is the same answer a dialect problem gets, and it is what keeps a bare ValueError out of turn().

merge_model_usage(first, second)

Sum the per-model usage of two separate turns into one.

This adds turns that each have their own rows. Rows a parser built from a CLI's cumulative counts already cover the whole run and must not be added.

Rows with the same model and speed add up, and a sum is unpriced if either part was. The primary row of first (else of second) stays the only primary, so a merged run still has exactly one.

model_spec(provider, model, table=None, *, catalog=None)

The facts about model run through provider, or None when they are not known.

Accepts a provider name (codex, claude) or a vendor name, and a model with a vendor/ prefix (opencode's provider/model). Drops a trailing date snapshot (claude-haiku-4-5-20251001). Returns None for a bracketed variant (claude-opus-5-5[1m]), for a provider that runs no known vendor's models, and for any model the table lacks; it never matches by prefix or family.

normalize(schema, dialect)

Return a copy of schema rewritten into the provider's house style.

Generators such as Pydantic omit defaulted properties from required and leave additionalProperties unset, which the CLIs read as "any subset of these keys, plus anything else". This closes every object, requires every declared property and drops default. Descriptions, titles and examples are retained at every schema node: Codex and Claude accepted them in real CLI probes on 2026-10-04. For STRICT, annotations beside $ref move to an anyOf wrapper containing the reference; OPEN keeps them as siblings. Only metadata rejected by the dialect is removed ($schema and $id under STRICT). Other $ref siblings are stripped as before. An open map (additionalProperties a schema or true) is kept, since closing it would change what the schema accepts; so dialect_problems on the normalized schema reports exactly what the dialect cannot express. Callers that want the schema passed through untouched skip this.

normalized_usage(*, input_tokens, output_tokens, cache_read_input_tokens=0, cache_write_input_tokens=0, cache_write_1h_input_tokens=0, reasoning_output_tokens=0, turns=0)

Build a :class:TokenUsage from provider counts, clamping any that break an invariant.

Parsers use this so a CLI that reports inconsistent counts (a cache read larger than the input, say) yields the closest valid usage instead of failing the turn. Negative counts read as 0.

parse_json_object(line)

Decode one stdout line into a JSON object.

Returns None for blank lines, invalid JSON, and valid JSON that is not an object (a bare 42 or [1,2] on stdout is provider noise, not an event, and must not crash the turn).

parse_litellm(document, *, checked_on)

Read LiteLLM's model_prices_and_context_window.json into a :class:RateTable.

A model needs finite, non-negative input and output rates, or it is dropped; so is one with a malformed value, since a half-read price would understate cost. A missing cache read or write rate defaults to the input rate. Speed tiers come from provider_specific_entry.fast (a multiple of standard) or from the _priority (fast) and _ultrafast columns; a tier's missing cache rates scale by the standard ratio. Long-context, batch and flex columns are not read.

Names are trimmed and lowercased. A vendor-prefixed name also files its bare name, as an alias, unless that name is already a key. Where several prefixed names share a bare name, their speed tiers fill each other's gaps and the alias is dropped if any two differ.

Parameters:

Name Type Description Default
document bytes

The file's bytes.

required
checked_on date

The day the document was read.

required

Raises:

Type Description
RateDocumentError

The bytes are not a JSON object, or no model in it has usable rates.

parse_timestamp(value)

A transcript's ISO 8601 timestamp as an aware datetime, or None if it is not one.

Accepts Z and numeric offsets and any number of fractional digits (datetime.fromisoformat before Python 3.11 takes neither). A timestamp with no offset is read as UTC.

plan_auth(profile, policy, *, env, home)

Choose the credential profile's CLI will log in with under policy.

Walks the profile's credential_sources in the CLI's precedence order and takes the first one present on the host (a non-blank variable in env, or an existing file under home whose contents classify) whose kind the policy allows. Only that credential is delivered, so the CLI cannot prefer another.

Raises:

Type Description
AuthUnavailableError

the profile declares no credential sources, the policy names an endpoint the CLI cannot take or that accepts none of the allowed kinds, or no allowed credential is present.

prepare_config_home(profile, home, env)

Make home a state root holding only the credentials, and point at it.

Links each of profile.config_home_files that exists in the state root env selects into home as a symlink to that file's real path (replacing whatever was there, such as a copy from an older agentshim), so a token the CLI refreshes in either home is the one both read next. Returns the environment overrides that make the CLI use home. A sandbox that runs the CLI must expose the link's target read-write. Everything else in home (conversations, caches) is the CLI's own and is left alone. A provider with no config_home_files isolates by flags alone: nothing is written and the result is empty.

Raises:

Type Description
ValueError

home is not absolute, or is the state root env already selects, which would isolate nothing.

price_for(provider, model, table=None, *, catalog=None, context=None)

The price of model run through provider, or None when it is not known.

Never falls back to another model's price or to zero.

price_tokens(tokens, rates)

What tokens cost at rates, split by token class.

Every token is priced once. Reasoning tokens are part of the output count, so they are billed at the output rate and not added again.

probe_provider(provider, *, executor=None, confinement=None, env=None, timeout=15.0, catalog=DEFAULT_CATALOG)

Report the readiness of provider without running a turn.

Arguments mean what they mean for Agent: executor defaults to the local host, confinement wraps it and supplies the environment (so env may not also be given), and env otherwise defaults to the login shell's environment. timeout bounds each of the two commands (--version and the provider's status command).

A missing binary is a result (binary_found=False), not an error, so a startup check can report every provider in one pass. A command that cannot run or times out leaves that field unknown; it never raises for a CLI problem. An unknown provider raises ValueError.

provider_names(*, catalog=DEFAULT_CATALOG)

List names in catalog, sorted.

rates_for(table, provider, model, *, catalog=None)

The entry for model run through provider, or None when it has no rates.

The provider decides how its model names map onto the flat names in the table: a bracketed variant and a date snapshot are dropped, a vendor/ prefix is tried last, and a name that is not a model at all (a synthetic placeholder, a bare family name) has none. A provider the catalog does not know gets the generic rules. Never falls back to another model's rates.

read_limits(provider, *, executor=None, confinement=None, env=None, timeout=15.0, clock=None, catalog=DEFAULT_CATALOG)

Read provider's account limits now, without running a turn.

Arguments mean what they mean for :func:~agentshim.probe.probe_provider: executor defaults to the local host, confinement wraps it and supplies the environment (so env may not also be given), and env otherwise defaults to the login shell's environment. timeout bounds the whole read. clock measures it.

A read that cannot be done is a result, not an error: unavailable says UNSUPPORTED (the provider has no limits read), API_KEY (no plan applies), or PROBE_FAILED (no CLI, not logged in, timed out, an error answer; the detail says which). An unknown provider raises ValueError.

read_rates(store, *, now, catalog=None)

The best rates available without the network.

The stored document laid over the pinned table when it parses, otherwise the pinned table. Never fetches, so it cannot block on a dead network.

Raises:

Type Description
ValueError

now is naive.

read_transcript_usage(root, *, known, since, byte_budget, include_cwd=False, catalog=None)

Read the transcripts under root that may hold usage since since.

Parameters:

Name Type Description Default
root TranscriptRoot

One of :func:transcript_roots.

required
known Mapping[Path, FilePosition]

What the caller holds: the position returned for each file the last time it was read, for every root of the provider. A file with no entry is read from the start, unless a file of the same session under another path has one (see the module docstring).

required
since datetime

Start of the window. Files last modified more than 36 hours before it are skipped. A file inside the window is read whole, so its records can be older than since: filter on :attr:TranscriptRecord.timestamp to count only the window.

required
byte_budget int

Stop after about this many bytes (at least 1), at a line boundary. The first file read always makes progress, so repeating the call with the returned positions finishes for any budget.

required
include_cwd bool

Whether records carry the session's working directory, which is a path on the user's machine. Off by default, so records a caller stores or sends hold no personal path unless it asked.

False
catalog ProviderCatalog | None

Where to find the provider's format.

None

Returns:

Type Description
TranscriptScan

What is new. Passing every returned position back as known and

TranscriptScan

calling until pending is zero gives the same records as one read of

TranscriptScan

each file whole, however the file grew, the budget split the work or a

TranscriptScan

session file moved between the provider's directories.

Raises:

Type Description
ValueError

byte_budget is below 1, since is naive, or the root's provider has no transcript format.

refresh_rates(store, source, *, now, force=False, catalog=None)

Fetch a new document if one is due, and report the rates to use.

A fetch is due when the document is older than :data:RATES_TTL (or force), unless any attempt was made within :data:REFRESH_FLOOR. Each of :data:LITELLM_ENDPOINTS is tried in turn. A document that does not parse, or parses to no model, counts as a failed fetch and never replaces a good one. The attempt is recorded either way, so the floor holds after a failure too.

Raises:

Type Description
ValueError

now is naive. Checked before anything is fetched.

Exception

Whatever store raises when it cannot write.

speed_premium_usd(tokens, entry, speed)

How much more tokens cost at speed than at the standard tier.

Zero at the standard tier, and zero when the entry lists no rates for speed: no premium is known, so none is claimed.

stream_provider_names(*, catalog=DEFAULT_CATALOG)

List providers in catalog with long-lived transports.

transcript_providers(*, catalog=None)

The providers in catalog whose transcripts can be read, in name order.

transcript_roots(provider, env, *, catalog=None)

The directories provider keeps transcripts under, for the environment env.

Follows the CLI's own rule for its state root (an environment variable if set, else a directory under HOME). The directories may not exist. A provider whose transcripts agentshim cannot read has no roots.

Raises:

Type Description
ValueError

provider is not in the catalog.

use_reset_credit(provider, *, idempotency_key, credit_id=None, executor=None, confinement=None, env=None, timeout=15.0, clock=None, catalog=DEFAULT_CATALOG)

Spend one of the account's reset credits, then read the limits again.

This costs the account something real and cannot be undone. credit_id names the credit (ResetCredit.id); None lets the provider pick its next one. idempotency_key names one logical attempt: a caller that retries after an error it cannot read reuses the key, and the provider spends at most one credit for it. Make a new key (a UUID) for each separate decision to spend.

timeout bounds the spend and the read after it separately. The result carries the provider's outcome and the limits read afterwards, which can differ from what the outcome promises: trust the read.

Raises:

Type Description
ValueError

provider is not in the catalog, or idempotency_key is empty.

ProviderCapabilityError

provider has no reset credits.

(CliNotFoundError, TurnTimeoutError, TurnFailedError)

the call did not settle. The credit may or may not be spent: read the limits to find out, and retry with the same key if it is still wanted.

Test doubles

testing

Test doubles shipped for consumers.

Build agents with FakeExecutor and assert on TurnResult and the typed events; never on a provider's internal attributes.

Ask dataclass

The server asks the client something and waits for the reply.

AskKind

Bases: Enum

The server-to-client requests Ask can make.

AwaitSteer dataclass

The turn is busy (a long command, say) until the client steers it.

Real Codex lets the work in flight finish, then shows the model the steered message and the same turn goes on. Here the message becomes an item of the turn and the turn carries on with then in place of the steps that remain. A turn nobody steers waits like Hang until it is interrupted.

CallMcp dataclass

The agent calls an MCP tool. error makes the call fail with that message.

ChangeFile dataclass

The agent edits a file; like RunCommand, it may need approval.

ClaudeApiError dataclass

A turn that ends in an API error, the way Claude Code reports one.

The CLI writes a synthetic assistant message with an error kind and then a result with subtype success and is_error true. ends_process makes the process exit 1 afterwards (what one-shot mode does on purpose; whether a stream-json process survives is unverified).

ClaudeCrash dataclass

The process dies in the middle of a turn, after writing stderr.

ClaudeFramePeer

A fake claude that writes hand-written frames, one batch per user message.

Each entry of turns is what the CLI writes after a user message: JSON frames (mappings), GateMarker points a test holds back with ReplayGates, and ProcessExited. The answer to initialize is automatic. A batch that ends in a gate followed by more frames is how a test makes the CLI speak while no turn is running. Once the entries are spent a message gets no answer. Everything received on stdin is kept in received.

__init__(turns)

Take the batches, one per user message.

on_start()

Write nothing until initialized.

on_stdin(data)

Answer initialize and write the next batch per user message.

on_stdin_closed()

Exit cleanly at end of input.

ClaudePeerTurn dataclass

One scripted turn: what the CLI does for one user message.

structured_output is reported only when the process was started with --json-schema, like the real CLI. control_requests are requests the CLI sends mid-turn (can_use_tool, hook_callback, ...); the turn does not finish until every one is answered. permission_denials are listed in the result, as the CLI does for what it refused on its own. stall makes the turn run until it is interrupted (ignores_interrupt makes it ignore even that). A user message that arrives while the turn runs is, like the real CLI's, either folded in (injects_steer: the model is at a tool boundary, so the message is echoed and the next scripted turn continues this one, ending in a single result) or queued (the default: it runs as a turn of its own, with its own init and result, once this one has ended, however it ended). fail_subtype ends the turn with an error result of that subtype (error_max_structured_output_retries, error_during_execution, ...) whose errors hold text.

ClaudeRecordedPeer

Replays the stdout of a recorded run, one turn per user message.

The recording is split at its result frames. The answer to initialize is replayed with the request id the client actually used. A recording that begins with a result is a process that died at start (a refused resume): everything, and stderr, is emitted at once, then exit 1. Control requests other than initialize are not answered: a recording holds the answers it was made with, in place.

__init__(stdout, stderr='')

Split stdout (the recorded JSON lines) into its turns.

on_start()

Replay a recording of a process that died at start.

on_stdin(data)

Answer initialize and replay the next recorded turn per user message.

on_stdin_closed()

Exit cleanly at end of input.

ClaudeStreamPeer

One fake claude process. Records everything it is sent.

__init__(world, argv)

Read the flags that change behaviour from argv.

on_start()

Refuse a resume of a conversation the CLI does not have; otherwise stay quiet.

on_stdin(data)

React to each JSON line in data.

on_stdin_closed()

Exit cleanly at end of input.

ClaudeStreamPeers

Builds the peers of a fake claude and remembers its conversations.

script holds one entry per user message, consumed across all processes in order; once spent, every turn is a plain ClaudePeerTurn(). known_sessions are conversations the CLI already has (resumable); every conversation a peer starts is added. skills is the list the init frame offers (None offers no list).

__init__(script=(), *, known_sessions=(), skills=None, drops_queued_messages=False, account=None, usage=None, mcp_status=None)

Bind the script and the conversations that already exist.

With drops_queued_messages the CLI forgets a message that arrived mid-turn and was not folded in, instead of running it afterwards. account is the account object initialize reports and usage the response of a get_usage request (see agentshim.testing.limits); without usage the CLI refuses get_usage as an unsupported request. mcp_status answers mcp_status requests: one entry per request, each mapping a server name to its (status, error) as the CLI words them ("connected", "pending", "failed", "needs-auth"); the last entry answers every later request. Without it the CLI refuses mcp_status as an unsupported request.

build(request)

The far end of the process request spawns (pass as FakeExecutor(peers=...)).

new_session_id()

Name a conversation: session-1, session-2, ...

next_mcp_status()

The mcpServers of the next mcp_status answer, or None if it is refused.

next_turn()

Take the next scripted turn, or a plain one once the script is spent.

CodexAccount dataclass

The account behind a fake codex app-server: its limits and its reset credits.

buckets maps a limit id to its (primary, secondary) windows; the default is the single weekly bucket a Pro plan reports. credits are the ids of the reset credits it holds. read_error makes account/rateLimits/read fail with that message. outcomes holds the answer given to each idempotency key already used; spent lists the credits spent, in order. Spending a credit empties every window.

consume(idempotency_key, credit_id)

Spend a credit for idempotency_key, once: a repeat of a key repeats its answer.

held()

The credits not yet spent.

rate_limits(*, credit_details=True)

The result of account/rateLimits/read.

CodexAppServerPeer

One fake codex app-server process (a FakePeer).

__init__(script)

Serve script. Made by CodexScript.peer.

on_start()

A real server says nothing until spoken to.

on_stdin(data)

Answer the lines in data.

on_stdin_closed()

Exit cleanly, as the real server does at end of input.

CodexScript

What a fake Codex app-server does, and what it was asked.

Queue turns with turn; a turn that is asked for when none is queued answers "ok". peer is the factory for FakeExecutor(peers=...): every process it makes shares this script's rollout store.

inherited_sandbox and inherited_approval stand for the user's own config.toml: they apply to a request that names nothing. With ignore_requested_permissions the server applies them even to a request that does name something, to test a client that checks what was applied. With exits_at_end_of_input=False a server ignores the closing of its stdin and has to be terminated. With steer_refusal the server refuses every turn/steer with that message, as it does for a turn that cannot be steered.

__init__(*, model='fake-model', inherited_sandbox='danger-full-access', inherited_approval='never', ignore_requested_permissions=False, exits_at_end_of_input=True, steer_refusal=None, service_tier=None, account=None)

Start with no queued turns, no threads and nothing received.

service_tier is the tier the server reports when it starts or resumes a thread ("priority" bills at the fast rate). account is the plan behind the server: its limits and reset credits, answered by account/rateLimits/read and account/rateLimitResetCredit/consume (see agentshim.testing.limits); without it both are unsupported.

add_thread(thread_id, *, cwd='/work')

Put a thread in the rollout store, as if an earlier process had made it.

counter(prefix)

The next id for prefix: thread-1, turn-2, ...

fail_mcp_server(name, error='failed to start')

Make the MCP server name report a start-up failure on every thread.

forget(thread_id)

Drop a thread, as if its rollout had been deleted.

mcp_failure(name)

The start-up error scripted for MCP server name.

next_turn()

Take the next queued turn, or a plain "ok".

peer(request=None)

A fresh server process on this script; pass as FakeExecutor(peers=...).

requests(method)

The requests the client sent for method, in order.

store(thread)

Keep thread in the rollout store.

thread(thread_id)

The stored thread, if any.

thread_ids()

The threads in the rollout store.

turn(*steps)

Queue one turn made of steps; returns the script for chaining.

CodexWindow dataclass

One window of a Codex bucket: the share used and its length and refill time.

wire(*, used_percent=None)

The RateLimitWindow object.

Complain dataclass

A line on stderr.

Crash dataclass

The process dies, with a line on stderr.

EchoPeer

Like cat: echoes stdin to stdout line by line, exits 0 at end of input.

on_start()

Produce nothing.

on_stdin(data)

Echo each line of data as stdout, keeping its newline.

on_stdin_closed()

Exit cleanly.

Fail dataclass

The turn fails. info is the codexErrorInfo: a name or a one-key object.

FakeCheckpointStore

Bases: InMemoryCheckpointStore

An in-memory store that also records every call made to it.

__init__()

Start empty with no calls recorded.

clear(key)

Record and apply a clear.

load(key)

Record and answer a load.

save(key, checkpoint)

Record and store a save.

FakeClock

A Clock whose waits take no real time.

monotonic starts at 0. wait advances it by the requested seconds at once, unless stop is already set, and records the request in waits.

__init__()

Start at time zero with no waits recorded.

advance(seconds)

Move simulated time forward without recording a wait.

monotonic()

Return the simulated time.

wait(seconds, stop=None)

Advance by seconds and return False; return True at once if stopped.

FakeCommandHandle

Handle that records the stop calls made against it.

__init__()

Start with nothing recorded.

kill()

Record a kill call.

terminate()

Record a terminate call.

FakeConfinement

Wraps argv behind a fixed prefix and records what it was asked.

wraps holds each (argv, cwd) seen and reaps counts reap calls. Paths map by path_map (longest prefix) and are otherwise identity.

env property

The configured environment.

__init__(*, path_map=None, env=None, prefix=('fake-confine',))

Configure the path mapping, environment and argv prefix.

agent_path(host_path)

Map host_path through path_map.

reap()

Count the call.

wrap(argv, cwd)

Record the call and return the prefix followed by argv.

FakeConversation

A Conversation that follows its transport's script.

closed property

Whether close has been called.

conversation_id property

The conversation's id, once it has one.

__init__(transport, spec)

Start with the spec's resume_id as the conversation id.

close()

Record the close; idempotent.

interrupt()

Mark the running turn interrupted; with none running, do nothing.

steer(text)

Accept text into the running turn; report it as delivered and consumed.

Raises NoRunningTurnError with no turn running. The events are emitted on the turn's thread at its next step, like a real transport's.

turn(request, emit)

Follow the next scripted outcome.

FakeExecutor

CommandExecutor that replays scripted runs instead of spawning.

runs is either a list consumed in order (the last one repeats once exhausted) or a callable that picks a run per request.

__init__(runs, *, binaries=None, peers=None, gates=None)

Take one run, a sequence of runs, or a callable that picks per request.

binaries pins what find_binary answers; without it every name resolves to a plausible path. peers builds the far end of each spawn from its request; without it spawn raises ValueError. gates is shared by every spawned FakeProcess.

check_binary(path, env, *, timeout)

Record the check; the fake never inspects the binary.

find_binary(name, env)

Return the pinned path for name, or a plausible default one.

run(request, sink)

Replay the next scripted run, driving sink like a real one would.

spawn(request)

Start a FakeProcess whose peer the peers factory builds.

FakePeer

Bases: Protocol

The far end of a FakeProcess: reacts to what the process is sent.

Each method returns the output the process produces in response, in order. A peer ends the process by returning ProcessExited. A protocol fake (JSON-RPC, say) answers by request id from on_stdin.

on_start()

Output produced as soon as the process starts.

on_stdin(data)

Output produced in response to data written to stdin.

on_stdin_closed()

Output produced when stdin is closed (a filter usually exits here).

FakeProcess

A Process driven by a FakePeer, with no real waiting.

next_output returns buffered items in order; when none is available (the buffer is empty, or a closed gate holds the next one) it returns None at once, standing in for a timeout. Once the peer exits, or the process is terminated or killed, ProcessExited follows and repeats. A kill or terminate discards undelivered output: the process died before anyone read it. Writes and stop calls are recorded for assertions.

__init__(peer, *, gates=None)

Start the process and buffer whatever the peer says on start.

close_stdin()

Tell the peer input ended, once.

kill()

Record the call and end the process with SIGKILL's code.

next_output(timeout)

Return the next deliverable item, or None (a simulated timeout).

terminate()

Record the call and end the process with SIGTERM's code.

wait(timeout)

Return the exit code once the exit is known, else None.

write(data)

Record data and let the peer react, or raise if stdin is unusable.

FakeRateSource

A RateSource that answers from a script instead of the network.

Each call to :meth:fetch consumes the next outcome: bytes are returned, a :class:RateFetchError is raised. When the script runs out every call fails, as a dead network would.

__init__(*outcomes)

Script the outcomes, first call first.

fetch(endpoint)

Return or raise the next scripted outcome.

FakeRateStore

A RateStore that keeps its snapshot in memory.

__init__(snapshot=None)

Start holding snapshot, or nothing.

read()

The snapshot last written, or the one it started with.

write(snapshot)

Replace the snapshot, remembering the write.

FakeRun dataclass

One scripted process run.

FakeTransport

A Transport whose conversations follow one script, in turn order.

script is consumed one entry per turn across all conversations; a FakeTurn succeeds and an AgentShimError is raised. Once it runs out every turn succeeds. resumable limits which ids can be resumed (any, by default); resuming another raises SessionResumeError at the first turn, or at open with refuse_at_open. Everything opened is recorded.

profile property

The fake provider's profile.

__init__(script=(), *, profile=None, resumable=None, refuse_at_open=False)

Bind the script and the resume behavior.

can_resume(conversation_id)

Whether the fake provider still has conversation_id.

new_conversation_id()

Name a new conversation: conv-1, conv-2, ...

next_outcome()

Take the next scripted outcome, or a plain success once the script is spent.

open(spec)

Record spec and start a conversation, rejecting an unsupported mode.

FakeTurn dataclass

A scripted successful turn.

conversation_id is the id the result names; by default the conversation keeps the id it has, or is given a fresh one on its first turn. during runs in the middle of the turn on the calling thread: call session.interrupt() from it to interrupt deterministically.

GateMarker dataclass

A point in a peer's output that a closed gate holds back.

Not a ProcessOutput: consumers of a process never see it.

Hang dataclass

The turn runs until the client interrupts it.

With ignores_interrupt the server acknowledges an interrupt but never reports the turn over, as a wedged one would.

Leave dataclass

The agent starts a shell command that never reports completion.

The turn goes on and ends with the item still inProgress: Codex does not always send item/completed for an item an interrupt or an error cut short.

RecordingEventHandler dataclass

Collects every event a turn emitted, in order.

of_type(kind)

Return the recorded events that are instances of kind.

on_event(event)

Append the event to events.

ReplayGates

Named gates a simulation closes to pause a conversation at a known point.

Every gate starts open. A peer emits GateMarker(name); while that gate is closed, the FakeProcess delivers nothing past the marker.

__init__()

Start with every gate open.

close(name)

Hold back output past markers named name.

is_open(name)

Whether name lets output through.

open(name)

Release output held at markers named name.

ReportRateLimits dataclass

The server reports the account's rate limits: account/rateLimits/updated.

primary and secondary are (used_percent, window_minutes, resets_at) or None for a window the server does not report.

Retrying dataclass

The stream broke and Codex is reconnecting: a non-terminal error.

RunCommand dataclass

The agent runs a shell command.

With approval the command needs the user's approval when the thread's approval policy asks (anything but never): the server sends an approval request and waits for the client's reply. A declined or cancelled command does not run.

Say dataclass

The agent says something. final_answer is the turn's answer, commentary narration.

SequentialIds

An IdAllocator that numbers ids per prefix: turn-1, turn-2, ...

__init__()

Start every prefix at 1.

new_id(prefix)

Return <prefix>-<n> with n counting up from 1 for this prefix.

SilentPeer

A peer that never writes and never exits on its own.

on_start()

Produce nothing.

on_stdin(data)

Produce nothing.

on_stdin_closed()

Produce nothing.

Spend dataclass

The model request used tokens. The thread's running total grows by this much.

Think dataclass

The agent reasons.

TokenUsage dataclass

Token counts for one turn, normalized across providers (see module docstring).

Invariants, checked on construction: cache_read_input_tokens + cache_write_input_tokens <= input_tokens, cache_write_1h_input_tokens <= cache_write_input_tokens and reasoning_output_tokens <= output_tokens.

cached_input_tokens is kept equal to cache_read_input_tokens: pass either one. Before 0.7.0 it also counted cache writes on Claude, opencode and Copilot; :meth:from_dict reads such records back.

uncached_input_tokens property

Input billed at the base rate: neither read from nor written to the cache.

__add__(other)

Accumulate a running total over the turns of a session.

Every field is summed, turns included, so folding per-turn usages together yields both the token totals and the turn count.

__post_init__()

Mirror the deprecated alias and check every invariant.

from_dict(data) classmethod

Read back a :meth:to_dict mapping, including one written before 0.7.0.

A mapping without cache_read_input_tokens predates the split: its cached_input_tokens counted cache writes too (Claude, opencode, Copilot; Codex reported none), so the writes are subtracted back out. Unknown keys are ignored; missing or non-numeric counts read as 0.

to_dict()

Flatten the counts into a JSON-serializable mapping keyed by field name.

Includes the derived uncached_input_tokens. Keys are only ever added, so a reader of an older mapping keeps working.

weighted_total(weights)

Sum every token class times its weight.

The classes partition the turn (uncached input, cache reads, default-TTL cache writes, one-hour cache writes, non-reasoning output, reasoning output), so each token is weighted exactly once.

claude_account(*, email='person@example.com', subscription='Claude Team', api_provider='firstParty')

The account object in the reply to Claude Code's initialize.

claude_usage(*, five_hour=None, seven_day=None, model_scoped=(), seven_day_opus=None, seven_day_sonnet=None, plan='team', available=True)

The response of Claude Code's get_usage control request.

Windows left None are null in the reply, as for an account that has no such window. available=False is the reply for a login that has no plan limits (an API key): rate_limits_available false and rate_limits null.

fake_profile(*, supports_resume=True, supports_steer=False)

A profile for the fake provider: bypass-only, no renewal, resumable, not steerable.

installed_mcp_servers(provider, request, workspace, *, catalog=DEFAULT_CATALOG)

Return the MCP servers request installed for one turn, keyed by name.

Call this from inside a FakeExecutor run callback, while the turn that installed the servers is still running: a config-file provider's config file exists only for the lifetime of the turn, since the library restores it from the session's finally once the run returns, and a CLI-flags provider's servers live only in the one argv the run received.

Each entry is a plain dict in a canonical shape, regardless of how the provider itself renders it: command, args and env for a server started over stdio, url and transport for one reached over HTTP. Either shape includes tool_timeout_s when the invocation set one.

Parameters:

Name Type Description Default
provider str

A registered provider name.

required
request CommandRequest

The CommandRequest the executor's run callback received for this turn.

required
workspace Path

The turn's MCP workspace (its cwd, unless TurnRequest.mcp_workspace overrode it). Unused for a CLI-flags provider.

required
catalog ProviderCatalog

Provider implementations and test adapters to use.

DEFAULT_CATALOG

Returns:

Type Description
dict[str, dict[str, Any]]

One canonical entry per installed server.

probe_executor(provider, *, version='1.2.3', auth=AuthState.KNOWN_OK, installed=True, catalog=DEFAULT_CATALOG)

A FakeExecutor that answers provider's probe commands like the real CLI.

installed=False makes the binary missing. A command the probe is not declared to run exits 2 with a usage error, so a probe that strays shows up in executor.requests and in the status.

resume_refused(conversation_id='gone')

A scripted refusal to resume a conversation.

scripted_failure(provider, kind, *, session_id=None, catalog=DEFAULT_CATALOG)

Build a FakeRun for a turn that fails and classifies as kind.

The run is provider's real report of such a failure (an overload, a usage limit, a rejected login, output that never matched the requested schema, or an unclassifiable failure), so the raised CliExitError carries kind through the real parser. A consumer can therefore test its retry policy without knowing how any CLI phrases a failure.

Parameters:

Name Type Description Default
provider str

A provider whose stream reports failure kinds (claude or codex); any other raises ValueError, as does FailureKind.SCHEMA on codex, whose decoding is constrained to the schema.

required
kind FailureKind

The FailureKind the raised error must carry.

required
session_id str | None

The conversation the failed run names, if any.

None
catalog ProviderCatalog

Provider implementations and test adapters to use.

DEFAULT_CATALOG

Returns:

Type Description
FakeRun

A FakeRun with a nonzero exit code.

scripted_resume_failure(provider, *, session_id=None, catalog=DEFAULT_CATALOG)

Build a FakeRun for a resumed turn provider cannot continue.

Serve this to a session that has already adopt-ed a session id: the turn's resumed flag plus provider's own exit-classification rule (documented on docs/architecture.md, "Resume diagnosis is provider-dependent") is what turns the nonzero exit into SessionResumeError on claude, codex, gemini and opencode. Copilot gives no such signal, so the same run there stays a plain CliExitError. Served to a fresh (non-resumed) turn instead, every provider's rule falls back to a plain CliExitError too.

Finds the scripted failure through providers/<name>/scripted.py's resume_failure_lines, the same convention scripted_turn uses for scripted_lines, so a new provider cannot ship without one.

Parameters:

Name Type Description Default
provider str

A registered provider name.

required
session_id str | None

Folded into the scripted failure message for realism; it does not change what a raised error reports, which is always read from the resumed turn's own argv.

None
catalog ProviderCatalog

Provider implementations and test adapters to use.

DEFAULT_CATALOG

Returns:

Type Description
FakeRun

A FakeRun with a nonzero exit code.

scripted_turn(provider, *, text='', session_id=None, usage=None, tool_calls=(), structured_output=None, returncode=0, skills_offered=None, skills_invoked=(), catalog=DEFAULT_CATALOG)

Build a FakeRun whose stdout is provider's real stream format.

skills_invoked scripts one load per name, before tool_calls, the way provider shows a load, so the turn reports a SkillInvoked each. skills_offered scripts the offered list. Either raises ValueError on a provider whose stream cannot carry it (the matching profile.skill_* signal is NONE).

turn_failed(kind=FailureKind.OTHER, detail='failed')

A scripted failure of the given kind.

turn_timeout(timeout=1.0)

A scripted timeout.