Metadata-Version: 2.4
Name: soulacp
Version: 0.1.0
Summary: Python ACP client library — connect to AI coding agents (Claude Code, Gemini, OpenCode, OpenClaw, Cursor, Codex, Qwen, Kimi, Codebuddy, Cline, Copilot, Minion, Vibe, Nova, Crow) with zero session overhead
Author-email: AIXP Foundation <noreply@SoulACP.dev>
License: Apache-2.0
Project-URL: Homepage, https://soulacp.dev
Project-URL: Repository, https://github.com/AIXP-Foundation/SoulACP
Project-URL: Issues, https://github.com/AIXP-Foundation/SoulACP/issues
Project-URL: Documentation, https://soulacp.dev
Project-URL: Changelog, https://github.com/AIXP-Foundation/SoulACP/blob/main/CHANGELOG.md
Keywords: acp,agent-client-protocol,claude-code,gemini,codex,qwen-code,kimi,codebuddy,cline,copilot,minion,vibe,nova,crow,ai-agent,coding-agent,soulacp
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=1.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Dynamic: license-file

# soulacp

[![CI](https://github.com/AIXP-Foundation/SoulACP/actions/workflows/ci.yml/badge.svg)](https://github.com/AIXP-Foundation/SoulACP/actions/workflows/ci.yml)
[![Python](https://img.shields.io/pypi/pyversions/soulacp)](https://pypi.org/project/soulacp/)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)

Python ACP client library — connect to AI coding agents with zero session overhead.

## Features

- **15 CLI adapters**: Claude Code, Gemini, OpenCode, OpenClaw, Cursor, Codex, Qwen, Kimi, Codebuddy, Cline, Copilot, Minion, Vibe, Nova, Crow
- **No own session layer**: Directly manages CLI's native session
- **Zero token overhead**: No middleware, no duplicate session history
- **ManagedSession**: High-level API with auto session reuse, rotation, retry, fallback
- **Connection pooling**: Reuse idle connections, session matching
- **Session store**: Persistent user→session mapping with TTL (Memory or File cache)
- **Async streaming**: Real-time response chunks via asyncio
- **Auto-retry**: Exponential backoff with jitter
- **Host services**: Built-in file system and terminal services for CLI tool calls
- **Pure stdlib**: No external dependencies

## Install

```bash
pip install soulacp
```

## Quick Start

### ManagedSession (recommended)

```python
import asyncio
from soulacp import ManagedSession

async def main():
    async with ManagedSession(provider="claude", model="claude-sonnet-4-20250514") as session:
        # Simple query — session auto-managed
        response = await session.query("Hello!", user_id="user1")
        print(response)

        # Streaming
        async for chunk in session.stream("Write hello world", user_id="user1"):
            print(chunk, end="", flush=True)

asyncio.run(main())
```

ManagedSession automatically handles:
- **Session reuse**: Same user_id gets same CLI session across requests
- **Session rotation**: Fresh session on "prompt too long" overflow
- **Retry**: Exponential backoff on transient errors
- **Fallback**: Switch to alternate provider on persistent failure

### Low-level API

```python
import asyncio
from soulacp import ACPConfig, ACPConnectionPool, resolve_client_class

async def main():
    config = ACPConfig(provider="claude", model="claude-sonnet-4-20250514")
    client_class = resolve_client_class("claude")
    pool = ACPConnectionPool(config, client_class)

    async with pool.acquire() as (client, session_id):
        response = await client.query("Hello!")
        print(response)

    await pool.close_all()

asyncio.run(main())
```

## Supported Agents

| Agent | Provider | Model Example | CLI Command |
|-------|----------|---------------|-------------|
| Claude Code | `claude` | `claude-sonnet-4-20250514` | `claude` or `claude-code-acp` |
| Gemini | `gemini` | `gemini-3-flash-preview` | `gemini --acp` |
| OpenCode | `opencode` | — | `opencode acp` |
| OpenClaw | `openclaw` | — | `openclaw acp` |
| Cursor (ACP) | `cursor` | `cursor-acp/default` | `cursor-agent acp` |
| Cursor (Legacy) | `cursor-cli` | `cursor-cli/gpt-4` | `cursor-agent -p` |
| Codex | `codex` | `codex-acp/gpt-5` | `codex-acp` |
| Qwen Code | `qwen` | `qwen-acp/qwen-coder` | `qwen-code --acp --experimental-skills` |
| Kimi CLI | `kimi` | `kimi-acp/default` | `kimi acp` |
| Codebuddy Code | `codebuddy` | `codebuddy-acp/default` | `codebuddy-code --acp` |
| Cline | `cline` | `cline-acp/default` | `cline --acp` |
| GitHub Copilot | `copilot` | `copilot-acp/default` | `copilot --acp` |
| Minion Code | `minion` | `minion-acp/default` | `minion-code acp` |
| Mistral Vibe | `vibe` | `vibe-acp/default` | `vibe-acp` |
| Nova | `nova` | `nova-acp/default` | `nova acp` |
| Crow CLI | `crow` | `crow-acp/default` | `crow-cli acp` |

## Usage

### Claude Code

```python
import asyncio
from soulacp import ManagedSession

async def main():
    async with ManagedSession(provider="claude", model="claude-sonnet-4-20250514") as session:
        response = await session.query("Hello!")
        print(response)

asyncio.run(main())
```

### Gemini

```python
import asyncio
from soulacp import ManagedSession

async def main():
    async with ManagedSession(provider="gemini", model="gemini-3-flash-preview") as session:
        response = await session.query("Hello!")
        print(response)

asyncio.run(main())
```

### Multi-Agent

```python
import asyncio
from soulacp import ManagedSession

async def main():
    # Claude for code generation
    async with ManagedSession(provider="claude", model="claude-sonnet-4-20250514") as claude:
        code = await claude.query("Write a sorting algorithm in Python")

    # Gemini for code review
    async with ManagedSession(provider="gemini", model="gemini-3-flash-preview") as gemini:
        review = await gemini.query(f"Review this code:\n{code}")

asyncio.run(main())
```

## Session Management

### ManagedSession + ProviderSessionStore

ManagedSession uses ProviderSessionStore internally to map `(user_id, provider)` to CLI session IDs:

```python
from soulacp import ManagedSession

session = ManagedSession(provider="claude", model="claude-sonnet-4-20250514")

# First request — creates new CLI session, stores mapping
await session.query("Remember 42.", user_id="alice")

# Second request — reuses same CLI session (alice→session_abc)
await session.query("What number?", user_id="alice")

# Different user — gets different CLI session
await session.query("Hello!", user_id="bob")
```

### Custom Session Store

```python
from soulacp import ManagedSession, ProviderSessionStore, FileCache

# Persistent file-based session store
store = ProviderSessionStore(cache=FileCache("~/.soulacp/sessions.json"))
session = ManagedSession(provider="claude", model="claude-sonnet-4-20250514", session_store=store)
```

### Session Lifecycle

| Event | Behavior |
|-------|----------|
| First request | New CLI session created, mapping stored (TTL 7 days) |
| Subsequent requests | Same user_id → same CLI session (reuse) |
| "Prompt too long" | Auto-rotate to fresh session, clear old mapping |
| Connection error | Clear mapping, retry with new session |
| Fallback | Switch to alternate provider (e.g. claude→gemini) |

### Cache Backends

```python
from soulacp import MemoryCache, FileCache

# In-memory (default) — fast, lost on restart
memory = MemoryCache(max_size=10000)

# File-based — persists across restarts
file_cache = FileCache("~/.soulacp/sessions.json", debounce_seconds=1.0)
```

### Low-level Session Control

```python
from soulacp import ACPConnectionPool, ACPConfig, resolve_client_class

config = ACPConfig(provider="claude", model="claude-sonnet-4-20250514")
pool = ACPConnectionPool(config, resolve_client_class("claude"))

# Explicit session reuse
async with pool.acquire() as (client, sid):
    await client.query("Remember 42.")

async with pool.acquire(session_id=sid) as (client, sid2):
    assert sid2 == sid  # Same CLI session
    response = await client.query("What number?")
```

## Configuration

### Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `ACP_PROVIDER` | — | Provider name |
| `ACP_MODEL` | — | Model identifier |
| `ACP_POOL_SIZE` | 10 | Max pool connections |
| `ACP_TIMEOUT_CONNECT` | 30 | Connection timeout (seconds) |
| `ACP_TIMEOUT_PROMPT` | 3600 | Prompt timeout (seconds) |
| `ACP_AUTO_APPROVE` | true | Auto-approve tool permissions |
| `ACP_MAX_RETRIES` | 3 | Max retry attempts |

### Programmatic

```python
from soulacp import ACPConfig

config = ACPConfig(
    provider="claude",
    model="claude-sonnet-4-20250514",
    pool_size=5,
    timeout_connect=60,
    auto_approve_permissions=True,
    enable_fallback=True,
)
```

### From Environment

```python
from soulacp import ACPConfig

config = ACPConfig.from_env()
```

## Architecture

```
ManagedSession (high-level API)
  ├── ProviderSessionStore (user→session mapping)
  │   └── CacheBackend (MemoryCache / FileCache)
  ├── ACPConnectionPool (connection reuse + health check)
  │   └── ACPClientBase (JSON-RPC over stdio subprocess)
  │       ├── ClaudeACPClient      ├── ClineACPClient
  │       ├── GeminiACPClient      ├── CopilotACPClient
  │       ├── OpenCodeACPClient    ├── MinionACPClient
  │       ├── OpenClawACPClient    ├── VibeACPClient
  │       ├── CursorACPClient      ├── NovaACPClient
  │       ├── CodexACPClient       ├── CrowACPClient
  │       ├── QwenACPClient        └── CursorCLIClient (legacy)
  │       ├── KimiACPClient
  │       └── CodebuddyACPClient
  └── Services
      ├── FSService (file system operations)
      └── TerminalService (subprocess execution)
```

## Testing

```bash
# Unit tests (no CLI required)
pytest tests/ --ignore=tests/test_integration*.py -v

# Integration tests (requires specific CLI installed, auto-skips if not available)
pytest tests/test_integration.py -v          # Claude Code
pytest tests/test_integration_gemini.py -v   # Gemini
pytest tests/test_integration_codex.py -v    # Codex

# All tests
pytest tests/ -v
```

## License

Apache-2.0

Copyright 2026 AIXP Foundation [AIXP.dev](https://aixp.dev) | [SoulACP.dev](https://soulacp.dev)
