feat: vibed out some slop over here also

This commit is contained in:
h
2026-05-19 11:20:14 +02:00
commit bf6116dc8b
34 changed files with 6531 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
"""Minimal end-to-end example: one turn against a real `claude` CLI.
Prerequisites:
1. `claude` is on PATH and the user is logged in (`claude /login` once).
2. Your subscription is healthy (no rate limit, no auth block).
Run:
python examples/basic_usage.py
"""
from __future__ import annotations
import asyncio
import os
from pathlib import Path
from claude_code_api import (
AssistantMessage,
BackendOptions,
ClaudeCodeBackend,
ResultMessage,
TextBlock,
)
CWD = Path(os.environ.get("CLAUDE_CODE_CWD", Path.cwd())).resolve()
async def main() -> None:
opts = BackendOptions(cwd=str(CWD), dangerously_skip_permissions=True)
async with ClaudeCodeBackend(opts) as backend:
async for event in backend.complete(
[{"role": "user", "content": "Reply with the single word: OK"}]
):
if isinstance(event, AssistantMessage):
for block in event.content:
if isinstance(block, TextBlock):
print(f"[assistant] {block.text.strip()}")
elif isinstance(event, ResultMessage):
print(
f"[result] stop_reason={event.stop_reason} "
f"duration_ms={event.duration_ms}"
)
if __name__ == "__main__":
asyncio.run(main())
+67
View File
@@ -0,0 +1,67 @@
"""Tool calling via a stdio MCP server.
`BackendOptions.mcp_servers` materializes into a temp `--mcp-config` JSON
file under the hood. The model decides when to invoke the tool, claude's
TUI runs the round-trip, and the events stream surfaces a `ToolUseBlock`
plus its `ToolResultBlock`.
This example wires the bundled echo server at `scripts/echo_mcp_server.py`
— same one the smoke test uses.
Run:
python examples/mcp_tool.py
"""
from __future__ import annotations
import asyncio
import os
import sys
from pathlib import Path
from claude_code_api import (
AssistantMessage,
BackendOptions,
ClaudeCodeBackend,
ResultMessage,
TextBlock,
ToolResultBlock,
ToolUseBlock,
UserMessage,
)
REPO_ROOT = Path(__file__).resolve().parent.parent
ECHO_SCRIPT = REPO_ROOT / "scripts" / "echo_mcp_server.py"
CWD = Path(os.environ.get("CLAUDE_CODE_CWD", Path.cwd())).resolve()
async def main() -> None:
opts = BackendOptions(
cwd=str(CWD),
dangerously_skip_permissions=True,
mcp_servers={
"echo": {"command": sys.executable, "args": [str(ECHO_SCRIPT)]},
},
)
async with ClaudeCodeBackend(opts) as backend:
prompt = (
"Call the `mcp__echo__echo` tool with text='ping' and then "
"tell me what it returned. Reply with one short sentence."
)
async for event in backend.complete([{"role": "user", "content": prompt}]):
if isinstance(event, AssistantMessage):
for block in event.content:
if isinstance(block, TextBlock) and block.text.strip():
print(f"[assistant] {block.text.strip()}")
elif isinstance(block, ToolUseBlock):
print(f"[tool_use] {block.name}({block.input})")
elif isinstance(event, UserMessage) and isinstance(event.content, list):
for block in event.content:
if isinstance(block, ToolResultBlock):
print(f"[tool_res] {block.content!r}")
elif isinstance(event, ResultMessage):
print(f"[result] stop_reason={event.stop_reason}")
if __name__ == "__main__":
asyncio.run(main())
+69
View File
@@ -0,0 +1,69 @@
"""Multi-turn: same backend, two turns, context persists across the pair.
The backend fingerprints `messages[:-1]` against its pool of live PTYs.
After turn 1 finishes, we re-send the conversation with the assistant's
reply appended and one new user message — fingerprint matches, the same
process is reused, and the server-side prompt cache keeps the context
without us paying a fresh-spawn tax.
Run:
python examples/multi_turn.py
"""
from __future__ import annotations
import asyncio
import os
from pathlib import Path
from typing import Any
from claude_code_api import (
AssistantMessage,
BackendOptions,
ClaudeCodeBackend,
ResultMessage,
TextBlock,
)
CWD = Path(os.environ.get("CLAUDE_CODE_CWD", Path.cwd())).resolve()
def assistant_text(events: list[Any]) -> str:
for ev in reversed(events):
if isinstance(ev, AssistantMessage):
return "".join(b.text for b in ev.content if isinstance(b, TextBlock)).strip()
return ""
async def run_turn(
backend: ClaudeCodeBackend, history: list[dict[str, Any]]
) -> str:
events: list[Any] = []
async for event in backend.complete(history):
events.append(event)
if isinstance(event, ResultMessage):
print(f" → stop_reason={event.stop_reason} duration_ms={event.duration_ms}")
return assistant_text(events)
async def main() -> None:
opts = BackendOptions(cwd=str(CWD), dangerously_skip_permissions=True)
async with ClaudeCodeBackend(opts) as backend:
history: list[dict[str, Any]] = [
{"role": "user", "content": "Remember the codeword: Beaver. Reply OK."}
]
print("[turn 1]")
reply1 = await run_turn(backend, history)
print(f" [assistant] {reply1}")
history += [
{"role": "assistant", "content": [{"type": "text", "text": reply1}]},
{"role": "user", "content": "What was the codeword? Reply with one word."},
]
print("[turn 2]")
reply2 = await run_turn(backend, history)
print(f" [assistant] {reply2}")
if __name__ == "__main__":
asyncio.run(main())