Skip to main content

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 add claude --resume <session_id> ...
  • Codex: first call is like codex exec ..., later calls add codex exec resume <thread_id> ...
  • Gemini: first call is like gemini ..., later calls add gemini --resume <session_id> ...
  • Opencode: first call is like opencode run ..., later calls add opencode 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.argv is the complete provider CLI command. argv[0] is the value returned by find_binary.
  • CommandRequest.stdin is the prompt text to write to the command's standard input, then stdin should be closed.
  • CommandRequest.cwd, env, and timeout should be honored by the executor.
  • Call sink.started(handle) once after the command starts. The handle only needs terminate() and kill().
  • Call sink.stdout(line) and sink.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:

  • ClaudeCodeCodingAgent
  • CodexCodingAgent
  • GeminiCodingAgent
  • OpencodeCodingAgent

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:

  • HttpMcpServer is for HTTP/SSE-backed MCP servers.
  • StdioMcpServer is 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 via get_provider_class(...) and CodingAgent(provider=...).
  • register_provider(...) rejects invalid names, abstract classes, and accidental name collisions unless you pass overwrite=True.
  • If you want CodingAgent(...) to instantiate your provider, its constructor should accept the shared kwargs model, event_handler, event_handlers, mcp_servers, sandbox, and executor as needed.
  • If your provider needs extra constructor arguments beyond the shared portable set, pass them via backend_kwargs={...} when constructing CodingAgent(...).

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

agentshim-0.5.0.tar.gz (220.2 kB view details)

Uploaded Source

Built Distribution

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

agentshim-0.5.0-py3-none-any.whl (49.4 kB view details)

Uploaded Python 3

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

Hashes for agentshim-0.5.0.tar.gz
Algorithm Hash digest
SHA256 be24e08129532c097fef446aa087c521c40b8278884a36198d33cb20c2beb0fd
MD5 bce85c6e9d71cbe6b7c6ad4e839c8fb8
BLAKE2b-256 351f97290a8120021a388edcab7a27bc9aa1db0ffb67b1efcffde5e24ee4a3fb

See more details on using hashes here.

Provenance

The following attestation bundles were made for agentshim-0.5.0.tar.gz:

Publisher: publish.yml on vic-lsh/agentshim

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

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

Hashes for agentshim-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 74bbe6806b23807d5a1c42c013051c5785d38ac09ccc7416ae9fa39e7b1a8bdc
MD5 d30b7cc6ee87e74695ac5953733329c3
BLAKE2b-256 573f326d3e4830d26c93006d14a76170952d83855f01bf79749c9fa6ab2925ec

See more details on using hashes here.

Provenance

The following attestation bundles were made for agentshim-0.5.0-py3-none-any.whl:

Publisher: publish.yml on vic-lsh/agentshim

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

Supported by

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