Metadata-Version: 2.4
Name: pi-coding-agent-python-sdk
Version: 0.3.0
Summary: Python SDK facade for Pi Coding Agent sessions, services, resources, tools, and extensions
Requires-Python: >=3.11
Requires-Dist: pydantic<3,>=2.7
Provides-Extra: acp
Requires-Dist: agent-client-protocol<0.11,>=0.10; extra == 'acp'
Provides-Extra: test
Requires-Dist: jsonschema>=4.10; extra == 'test'
Requires-Dist: pytest-asyncio>=0.23; extra == 'test'
Requires-Dist: pytest>=8; extra == 'test'
Description-Content-Type: text/markdown

# Pi Coding Agent Python SDK

Python facade for creating Pi Coding Agent sessions, wiring resources, running the bundled TypeScript bridge, exposing MCP servers, and serving the agent through ACP.

The package published to PyPI is `pi-coding-agent-python-sdk`. The import package is `pi_coding_agent`.

## Install

```bash
pip install pi-coding-agent-python-sdk
```

ACP support is optional:

```bash
pip install "pi-coding-agent-python-sdk[acp]"
```

Provider-backed sessions use the bundled bridge worker and require either Node.js or Bun on `PATH`. The explicit `backend="in_process"` mode is a local deterministic mode for tests and offline examples.

### Claude Code skill (optional)

This repo ships a Claude Code skill at `skills/pi-coding-agent-sdk/` that bundles the SDK docs, all numbered SDK examples, and the hard-won-notes reference. Claude Code users can install it with one command:

```bash
npx -y skills add trotsky1997/pi-coding-agent-python-sdk
```

After install the skill auto-activates whenever a session imports `pi_coding_agent`, mentions `HarnessConfig`, or asks about embedding Pi sessions in a benchmark / harness.

## Quick Start

```python
import asyncio

from pi_coding_agent import MessageUpdateEvent, create_agent_session


async def main() -> None:
    result = await create_agent_session(cwd=".", backend="in_process")
    session = result.session
    try:
        session.subscribe(
            lambda event: print(event.assistant_message_event.delta, end="")
            if isinstance(event, MessageUpdateEvent)
            else None
        )
        await session.prompt("List the files in this directory.")
    finally:
        await session.dispose()


asyncio.run(main())
```

This first example is a local smoke test. For a provider-backed session, use `HarnessConfig` and keep secrets in environment variables:

```python
import asyncio

from pi_coding_agent import HarnessConfig, create_agent_session_from_config


config = HarnessConfig.from_dict(
    {
        "run": {"cwd": ".", "backend": "node"},
        "env": {
            "required": ["NOVITA_API_KEY"],
            "optional": ["NOVITA_BASE_URL"],
        },
        "model": {
            "provider": "novita",
            "id": "deepseek/deepseek-v4-flash",
            "api": "openai-completions",
            "base_url": "https://api.novita.ai/v3/openai",
            "api_key_env": "NOVITA_API_KEY",
            "context_window": 128000,
            "max_tokens": 32000,
            "reasoning": True,
            "compat": {
                "thinkingFormat": "deepseek",
                "requiresReasoningContentOnAssistantMessages": True,
            },
        },
        "provider_options": {
            "temperature": 0,
            "tool_choice": "auto",
            "parallel_tool_calls": True,
        },
        "tools": {"allow": ["read", "write", "edit", "bash", "grep", "find", "ls"]},
    }
)


async def main() -> None:
    result = await create_agent_session_from_config(config)
    try:
        await result.session.prompt("Inspect the project and summarize the test layout.")
    finally:
        await result.session.dispose()


asyncio.run(main())
```

## Harness TOML

`HarnessConfig` can load and dump TOML. It covers runtime selection, model/provider setup, environment passthrough, tool allowlists, resources, extensions, skills, MCP servers, hooks, and session storage.

```toml
version = 1

[run]
cwd = "."
backend = "node"

[js]
runtime = "bun"
request_timeout_sec = 30
tool_timeout_sec = 60
compact_timeout_sec = 300

[env]
required = ["NOVITA_API_KEY"]
optional = ["NOVITA_BASE_URL"]

[model]
provider = "novita"
id = "deepseek/deepseek-v4-flash"
api = "openai-completions"
base_url = "https://api.novita.ai/v3/openai"
api_key_env = "NOVITA_API_KEY"
context_window = 128000
max_tokens = 32000
reasoning = true

[model.compat]
thinkingFormat = "deepseek"
requiresReasoningContentOnAssistantMessages = true

[provider_options]
temperature = 0
tool_choice = "auto"
parallel_tool_calls = true

[tools]
allow = ["read", "write", "edit", "bash", "grep", "find", "ls"]

[mcp.servers.time]
command = "uvx"
args = ["mcp-server-time"]

[hooks.PreToolUse.block_rm]
matcher = "bash"
command = "python ./hooks/block_rm.py"
timeout = 5
condition = "bash(*)"
run_async = false

[session]
mode = "create"
```

```python
import asyncio

from pi_coding_agent import create_agent_session_from_config


async def main() -> None:
    result = await create_agent_session_from_config("pi-harness.toml")
    try:
        await result.session.prompt("What time tools are available?")
    finally:
        await result.session.dispose()


asyncio.run(main())
```

## Session Snapshots

Session state and harness config are separate by default. Persistent sessions are written by `SessionManager` as JSON under the configured session directory. Harness config is written as TOML and controls the runtime: model, env passthrough, tools, resources, extensions, skills, MCP, hooks, and session mode.

For a portable handoff, write both into one TOML file:

```python
from pi_coding_agent import HarnessConfig

config = HarnessConfig.load_toml("pi-harness.toml")
config.dump_with_session_snapshot("portable-harness.toml", result.session)
```

This embeds local resource files by default, stores the current session under `[session.snapshot]` as compressed base64 JSON, and writes the TOML with an atomic replace in the target directory. Loading the generated TOML restores the snapshot through an in-memory `SessionManager`, so it does not continue writing to the original `sessionFile`.

If you only need a config object:

```python
portable = config.with_session_snapshot(result.session, embed_resources=True)
toml_text = portable.to_toml()
```

External snapshot files are still supported:

```toml
[session]
snapshot_path = "session-snapshot.json"
```

Snapshot restore accepts the SDK JSON shape from `session.serialize()`, Pi JSONL session files, Harbor ATIF trajectory JSON, and AgentForge Qwen35 training records.

## Main Capabilities

- Session creation through `create_agent_session`, `create_agent_session_from_config`, `create_agent_session_services`, and `create_agent_session_runtime`.
- Built-in tools: `read`, `write`, `edit`, `bash`, `grep`, `find`, and `ls`.
- Python custom tools through `ToolDefinition`.
- TypeScript extension paths, package extension sources, and Python extension factories.
- Skills from local paths, GitHub sources through `npx skills add`, and Pi package-loader sources.
- Prompt templates, `AGENTS.md` discovery, system prompt overrides, and append-system-prompt files.
- MCP server configuration through `McpConfig` or TOML.
- Hooks for session, prompt, tool, compact, and stop events.
- Session JSON serialization, ATIF/Qwen35 import/export, and harness TOML snapshots.
- ACP server integration through `pi-coding-agent-acp`.

## Documentation

- [Harness Config](docs/harness-config.md)
- [Resources, Extensions, Skills, MCP, And Hooks](docs/resources.md)
- [ACP Integration](docs/acp.md)
- [Development And Release Notes](docs/development.md)
- [Example Parity Matrix](examples/sdk/PARITY.md)
- [Bridge Validation](examples/sdk/BRIDGE_VALIDATION.md)

## Development

Run the default suite with `uv`:

```bash
uv run -m unittest discover -s tests
```

Build and inspect the package:

```bash
uv build
uv run --with twine python -m twine check dist/*
```

The test suite is designed to run without provider credentials by default. Provider-backed tests are opt-in and should use environment variables, not checked-in secrets.
