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 |
command |
Sequence[str]
|
The hook's argv. Claude Code runs hooks through a shell, so
agentshim quotes it with |
matcher |
str | None
|
For tool events, the tool-name pattern the hook applies to,
such as |
timeout_s |
float | None
|
Seconds Claude Code allows the hook before giving up;
|
__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 ( |
allowed |
Sequence[str]
|
Permission rules ( |
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 |
required |
model
|
str | None
|
Provider-specific model id, or |
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 |
None
|
event_handler
|
AgentEventHandler | None
|
Merged with |
None
|
event_handlers
|
Sequence[AgentEventHandler]
|
Merged with |
()
|
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 |
TurnFailedError
|
The process would not start or answer. |
TurnTimeoutError
|
The handshake took longer than |
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
|
|
writable_roots |
Sequence[str]
|
Extra absolute directories commands may write.
|
network_access |
bool
|
Let commands open network sockets, including local
ones such as the Docker socket. |
writable_tmp |
bool
|
Keep |
excluded_commands |
Sequence[str]
|
Commands that run outside the sandbox, each written
as shell words, e.g. |
__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 |
required |
runner
|
CommandExecutor
|
Executor used to run |
required |
path_map
|
Mapping[str, str] | None
|
Host directory to container directory. |
None
|
env
|
Mapping[str, str]
|
Environment for the confined process. Each key is passed as
|
required |
user
|
str | None
|
Passed as |
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 |
guard_hash |
int
|
FNV-1a (32-bit) of those bytes. |
state |
bytes
|
The format's parser state at |
complete |
bool
|
Whether the read reached the end of the file. False when the
byte budget stopped it; the next call continues from |
__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 |
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 |
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 |
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 ( |
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 |
cache_write_1h_usd |
float | None
|
One-hour-TTL cache writes; |
reasoning_usd |
float | None
|
Reasoning output, when billed apart from output;
|
estimated |
bool
|
Whether any number is an estimate rather than a published
price; |
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 ( |
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 |
display_name |
str | None
|
A readable name for the model ( |
family |
str | None
|
The model line the model belongs to ( |
tier |
int | None
|
The model's rank within its family, |
tier_name |
str | None
|
The tier's readable name ( |
generation_id |
str | None
|
A short stable token naming the model's generation
within its vendor ( |
generation |
str | None
|
The generation as people call it ( |
generation_rank |
int | None
|
The generation's rank among its vendor's, |
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 |
__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 |
recorded_usd |
float | None
|
The pinned-table price of |
speed |
UsageSpeed | None
|
The service speed the turn ran at, on the primary model only;
|
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
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 |
__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)
|
|
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 |
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: |
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 |
fetched_at |
datetime | None
|
When the document behind |
known_models |
int
|
How many names |
refresh_due |
bool
|
Whether :func: |
RateSnapshot
dataclass
What a :class:RateStore keeps between runs.
Attributes:
| Name | Type | Description |
|---|---|---|
document |
bytes | None
|
The last document that parsed, as fetched; |
fetched_at |
datetime | None
|
When |
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; |
__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 |
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 |
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 |
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 |
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
|
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 |
suffix |
str
|
Which file names are transcripts. |
unreadable_suffixes |
tuple[str, ...]
|
Transcript names agentshim cannot read (a
compressed archive). They are reported in |
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. |
__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. |
tokens |
TokenUsage
|
The request's tokens. |
speed |
UsageSpeed
|
The service speed it was billed at. |
reported_usd |
float | None
|
A cost the file states. |
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
|
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: |
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: |
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 |
TranscriptScan
|
calling until |
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 |
required |
workspace
|
Path
|
The turn's MCP workspace (its |
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 ( |
required |
kind
|
FailureKind
|
The |
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 |
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 |
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.