Provider-agnostic coding-agent CLI shims
Project description
agentshim
agentshim wraps coding-agent CLIs behind one small Python interface.
It is useful when you want to drive tools like Claude Code, Codex, Gemini, or Opencode from Python without writing provider-specific subprocess plumbing for prompting, session resumption, event parsing, or MCP configuration.
What It Includes
- a shared CLI agent abstraction with a provider registry
- adapters for Claude Code, Codex, Gemini, and Opencode
- stateful chat sessions that automatically resume provider-native threads
- injectable command executors for custom process launching or sandboxing
- MCP server config models for providers that support MCP
- sandbox settings helpers for Claude Code
- a lightweight LiteLLM client and subagent helper
Install
uv add agentshim
agentshim does not bundle the underlying agent CLIs. You still need the
provider tool you want to use installed and authenticated on your machine, for
example claude, codex, gemini, or opencode.
Getting Started
1. Use the Generic Agent Interface for Chat and Resume
If you want to choose a provider at runtime, instantiate CodingAgent
directly with a provider name.
from agentshim import CodingAgent
agent = CodingAgent(provider="claude", model="sonnet")
chat = agent.start_session(cwd=".")
first_reply = chat.generate("Summarize this repository.")
follow_up = chat.generate("Now list the three highest-risk modules.")
print(first_reply)
print(follow_up)
print(chat.session_id)
start_session() returns a stateful chat object. On the first generate(...)
call, agentshim starts a fresh provider conversation. On later calls, it
automatically resumes the same underlying provider session using the session id
captured from the first run.
That corresponds roughly to these native CLI flows:
- Claude Code: first call is like
claude -p ..., later calls addclaude --resume <session_id> ... - Codex: first call is like
codex exec ..., later calls addcodex exec resume <thread_id> ... - Gemini: first call is like
gemini ..., later calls addgemini --resume <session_id> ... - Opencode: first call is like
opencode run ..., later calls addopencode run --session <session_id> ...
If you only want a one-shot request, use generate(...) directly instead of
opening a session:
from agentshim import CodexCodingAgent
agent = CodexCodingAgent(model="gpt-5")
reply = agent.generate("Write a short summary of this codebase.", cwd=".")
print(reply)
2. Handle Agent Events
By default, agentshim prints provider events to the terminal through a
ConsoleEventHandler. That default is used only when you do not provide your
own event handler and silent=False.
If you pass event_handler=..., you take ownership of event handling. The
built-in console printer is not added implicitly, which avoids surprising
duplicate output.
from agentshim import CodingAgent
class MyHandler:
def on_thinking(self, text: str) -> None:
...
def on_tool_call(self, tool: str, args=None) -> None:
...
def on_tool_result(
self,
tool: str,
stdout: str = "",
stderr: str = "",
exit_code: int | None = None,
duration: float | None = None,
) -> None:
...
def on_usage(self, usage: dict) -> None:
...
agent = CodingAgent(provider="claude", event_handler=MyHandler())
agent.generate("Inspect this repository.")
To keep the default console output and add your own handler, compose them explicitly:
from agentshim import CodingAgent, ConsoleEventHandler
agent = CodingAgent(
provider="claude",
event_handlers=[
ConsoleEventHandler(),
MyHandler(),
],
)
agent.generate("Inspect this repository.")
You can also build the composition yourself:
from agentshim import CompositeEventHandler, ConsoleEventHandler
handler = CompositeEventHandler([ConsoleEventHandler(), MyHandler()])
agent = CodingAgent(provider="codex", event_handler=handler)
Use silent=True to suppress the default console handler when you have not
provided any handler:
agent = CodingAgent(provider="claude")
reply = agent.generate("Return only the answer.", silent=True)
3. Run Commands Through a Custom Executor
Provider classes accept an optional executor=. The executor controls binary
lookup, CLI validation, and streaming process execution while agentshim keeps
owning provider command construction, stdout/stderr parsing, session state, and
event emission.
This is useful when a caller needs to run the CLI somewhere other than the current host process, for example through an existing container, remote shell, or custom sandbox.
from agentshim import (
CodexCodingAgent,
CommandHandle,
CommandRequest,
CommandResult,
CommandStreamSink,
)
class MyCommandHandle:
def terminate(self) -> None:
...
def kill(self) -> None:
...
class MyExecutor:
def find_binary(self, binary_name: str, env: dict[str, str]) -> str:
# This value becomes request.argv[0]. Container or remote executors can
# return the binary name if lookup happens in the target runtime.
return binary_name
def check_binary(self, binary_path: str, env: dict[str, str], *, timeout: int) -> None:
# Raise RuntimeError if the target CLI is unavailable. No-op is fine
# when validation is not cheap or is handled by the runtime.
return None
def run(self, request: CommandRequest, sink: CommandStreamSink) -> CommandResult:
handle = MyCommandHandle()
sink.started(handle)
stdout = ""
stderr = ""
# Run request.argv in your target runtime, with request.stdin,
# request.cwd, request.env, and request.timeout. Stream each complete
# line as it arrives, preserving trailing newlines when present.
line = "streamed output\n"
stdout += line
sink.stdout(line)
return CommandResult(returncode=0, stdout=stdout, stderr=stderr)
agent = CodexCodingAgent(executor=MyExecutor())
The default HostCommandExecutor preserves the normal local subprocess
behavior. CodingAgent(provider=..., executor=...) forwards the same executor
to the selected provider.
Executor contract:
CommandRequest.argvis the complete provider CLI command.argv[0]is the value returned byfind_binary.CommandRequest.stdinis the prompt text to write to the command's standard input, then stdin should be closed.CommandRequest.cwd,env, andtimeoutshould be honored by the executor.- Call
sink.started(handle)once after the command starts. The handle only needsterminate()andkill(). - Call
sink.stdout(line)andsink.stderr(line)as output is produced. Lines should include trailing newlines when the underlying stream provided them. - Return
CommandResult(returncode, stdout, stderr)after the command exits. The returned text should match what was streamed through the sink.
If you were using the pre-0.5 executor preview, replace
run_streaming(cmd, ..., on_stdout, on_stderr, on_process_started) with
run(request, sink). The parser/event APIs remain internal; custom executors
only provide a stable command runtime.
4. Instantiate a Specific Provider Directly
If you already know which backend you want, construct the provider class yourself.
from agentshim import ClaudeCodeCodingAgent
agent = ClaudeCodeCodingAgent(model="sonnet")
chat = agent.start_session(cwd=".")
print(chat.generate("What does this project do?"))
print(chat.generate("Which files should I read first?"))
The bundled provider classes are:
ClaudeCodeCodingAgentCodexCodingAgentGeminiCodingAgentOpencodeCodingAgent
5. Configure MCP Servers
Claude Code and Codex can be configured with MCP servers by passing
HttpMcpServer and StdioMcpServer objects at construction time.
from agentshim import ClaudeCodeCodingAgent, HttpMcpServer, StdioMcpServer
agent = ClaudeCodeCodingAgent(
model="sonnet",
mcp_servers=[
HttpMcpServer(
name="docs",
url="http://localhost:9000/sse",
headers={"Authorization": "Bearer dev-token"},
),
StdioMcpServer(
name="github",
command="npx",
args=["-y", "@modelcontextprotocol/server-github"],
env={"GITHUB_TOKEN": "ghp_example"},
),
],
)
chat = agent.start_session(cwd=".")
print(chat.generate("Use the MCP tools to inspect the repo."))
Notes:
HttpMcpServeris for HTTP/SSE-backed MCP servers.StdioMcpServeris for subprocess-backed MCP servers.- Gemini and Opencode currently reject
mcp_servers; use Claude Code or Codex if you need MCP.
Extending agentshim
Advanced users can register their own providers. CodingAgent(...) keeps its
main constructor portable; provider-specific constructor extras should go
through backend_kwargs.
from agentshim import BaseCodingAgent, CodingAgent, register_provider
@register_provider("my-agent", aliases=("my-agent-dev",))
class MyAgent(BaseCodingAgent):
def __init__(
self,
model: str | None = None,
region: str | None = None,
event_handler=None,
event_handlers=None,
mcp_servers=None,
sandbox=False,
):
self.model = model
self.region = region
self.event_handler = event_handler
def generate(self, prompt: str, cwd=None, timeout=300, silent=False) -> str:
return f"handled: {prompt}"
agent = CodingAgent(
provider="my-agent-dev",
model="demo",
backend_kwargs={"region": "us-west1"},
)
print(agent.generate("hello"))
Notes:
- Registration is import-driven. Your provider is available only after the module defining it has been imported in the current Python process.
list_providers()returns canonical provider names only. Aliases resolve viaget_provider_class(...)andCodingAgent(provider=...).register_provider(...)rejects invalid names, abstract classes, and accidental name collisions unless you passoverwrite=True.- If you want
CodingAgent(...)to instantiate your provider, its constructor should accept the shared kwargsmodel,event_handler,event_handlers,mcp_servers,sandbox, andexecutoras needed. - If your provider needs extra constructor arguments beyond the shared portable set, pass them via
backend_kwargs={...}when constructingCodingAgent(...).
Development
uv sync --dev
uv run pytest
Publishing
Build locally with:
uv build
Publish with:
uv publish
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agentshim-0.5.0.tar.gz.
File metadata
- Download URL: agentshim-0.5.0.tar.gz
- Upload date:
- Size: 220.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
be24e08129532c097fef446aa087c521c40b8278884a36198d33cb20c2beb0fd
|
|
| MD5 |
bce85c6e9d71cbe6b7c6ad4e839c8fb8
|
|
| BLAKE2b-256 |
351f97290a8120021a388edcab7a27bc9aa1db0ffb67b1efcffde5e24ee4a3fb
|
Provenance
The following attestation bundles were made for agentshim-0.5.0.tar.gz:
Publisher:
publish.yml on vic-lsh/agentshim
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agentshim-0.5.0.tar.gz -
Subject digest:
be24e08129532c097fef446aa087c521c40b8278884a36198d33cb20c2beb0fd - Sigstore transparency entry: 1385424782
- Sigstore integration time:
-
Permalink:
vic-lsh/agentshim@8a427ab34953429ec3ab2e59de2e91b3a67a335c -
Branch / Tag:
refs/heads/main - Owner: https://github.com/vic-lsh
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8a427ab34953429ec3ab2e59de2e91b3a67a335c -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file agentshim-0.5.0-py3-none-any.whl.
File metadata
- Download URL: agentshim-0.5.0-py3-none-any.whl
- Upload date:
- Size: 49.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
74bbe6806b23807d5a1c42c013051c5785d38ac09ccc7416ae9fa39e7b1a8bdc
|
|
| MD5 |
d30b7cc6ee87e74695ac5953733329c3
|
|
| BLAKE2b-256 |
573f326d3e4830d26c93006d14a76170952d83855f01bf79749c9fa6ab2925ec
|
Provenance
The following attestation bundles were made for agentshim-0.5.0-py3-none-any.whl:
Publisher:
publish.yml on vic-lsh/agentshim
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agentshim-0.5.0-py3-none-any.whl -
Subject digest:
74bbe6806b23807d5a1c42c013051c5785d38ac09ccc7416ae9fa39e7b1a8bdc - Sigstore transparency entry: 1385424873
- Sigstore integration time:
-
Permalink:
vic-lsh/agentshim@8a427ab34953429ec3ab2e59de2e91b3a67a335c -
Branch / Tag:
refs/heads/main - Owner: https://github.com/vic-lsh
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8a427ab34953429ec3ab2e59de2e91b3a67a335c -
Trigger Event:
workflow_dispatch
-
Statement type: