computer-agent-py 0.1.3__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/PKG-INFO +64 -2
  2. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/README.md +54 -1
  3. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/pyproject.toml +25 -2
  4. computer_agent_py-0.2.0/src/computeragent/__init__.py +163 -0
  5. computer_agent_py-0.2.0/src/computeragent/engines/__init__.py +56 -0
  6. computer_agent_py-0.2.0/src/computeragent/engines/claude_agent.py +154 -0
  7. computer_agent_py-0.2.0/src/computeragent/engines/git_agent.py +485 -0
  8. computer_agent_py-0.2.0/src/computeragent/harness/__init__.py +303 -0
  9. computer_agent_py-0.2.0/src/computeragent/harness/coordinator.py +721 -0
  10. computer_agent_py-0.2.0/src/computeragent/harness/errors.py +37 -0
  11. computer_agent_py-0.2.0/src/computeragent/harness/usage.py +63 -0
  12. computer_agent_py-0.2.0/src/computeragent/identity/__init__.py +47 -0
  13. computer_agent_py-0.2.0/src/computeragent/identity/gap.py +246 -0
  14. computer_agent_py-0.2.0/src/computeragent/identity/passthrough.py +66 -0
  15. computer_agent_py-0.2.0/src/computeragent/protocol_types/__init__.py +281 -0
  16. computer_agent_py-0.2.0/src/computeragent/session_stores/__init__.py +43 -0
  17. computer_agent_py-0.2.0/src/computeragent/session_stores/memory.py +48 -0
  18. computer_agent_py-0.2.0/src/computeragent/substrates/__init__.py +45 -0
  19. computer_agent_py-0.2.0/src/computeragent/substrates/local.py +126 -0
  20. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/config.py +12 -0
  21. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/sinks/agentos.py +71 -5
  22. computer_agent_py-0.1.3/src/computeragent/__init__.py +0 -90
  23. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/_proxy/__init__.py +0 -0
  24. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/_proxy/client.py +0 -0
  25. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/_proxy/query.py +0 -0
  26. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/policy/__init__.py +0 -0
  27. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/policy/authorizer.py +0 -0
  28. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/policy/cedar.py +0 -0
  29. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/policy/opa.py +0 -0
  30. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/policy/types.py +0 -0
  31. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/py.typed +0 -0
  32. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/__init__.py +0 -0
  33. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/event.py +0 -0
  34. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/middleware/__init__.py +0 -0
  35. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/middleware/guardrails.py +0 -0
  36. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/middleware/pii.py +0 -0
  37. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/pipeline.py +0 -0
  38. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/sinks/__init__.py +0 -0
  39. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/sinks/message_archive.py +0 -0
  40. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/sinks/otel.py +0 -0
  41. {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: computer-agent-py
3
- Version: 0.1.3
3
+ Version: 0.2.0
4
4
  Summary: Drop-in replacement for claude-agent-sdk that adds a proxied telemetry pipeline (PII redaction + guardrails) with OpenTelemetry and AgentOS sinks.
5
5
  Keywords: computeragent,claude-agent-sdk,claude,agent,telemetry,otel,opentelemetry,agentos,pii
6
6
  Author: Abhi Bhat
@@ -26,7 +26,14 @@ Requires-Dist: opentelemetry-exporter-otlp>=1.27 ; extra == 'all'
26
26
  Requires-Dist: opentelemetry-semantic-conventions>=0.48b0 ; extra == 'all'
27
27
  Requires-Dist: motor>=3.5 ; extra == 'all'
28
28
  Requires-Dist: cedarpy>=4,<5 ; extra == 'all'
29
+ Requires-Dist: openai>=1,<2 ; extra == 'all'
30
+ Requires-Dist: anthropic>=0.40,<1 ; extra == 'all'
31
+ Requires-Dist: pyyaml>=6,<7 ; extra == 'all'
29
32
  Requires-Dist: cedarpy>=4,<5 ; extra == 'cedar'
33
+ Requires-Dist: pyyaml>=6,<7 ; extra == 'gap'
34
+ Requires-Dist: openai>=1,<2 ; extra == 'gitagent'
35
+ Requires-Dist: anthropic>=0.40,<1 ; extra == 'gitagent'
36
+ Requires-Dist: pyyaml>=6,<7 ; extra == 'gitagent'
30
37
  Requires-Dist: opentelemetry-api>=1.27 ; extra == 'otel'
31
38
  Requires-Dist: opentelemetry-sdk>=1.27 ; extra == 'otel'
32
39
  Requires-Dist: opentelemetry-exporter-otlp>=1.27 ; extra == 'otel'
@@ -38,6 +45,8 @@ Project-URL: Issues, https://github.com/open-gitagent/computer-agent-py/issues
38
45
  Provides-Extra: agentos
39
46
  Provides-Extra: all
40
47
  Provides-Extra: cedar
48
+ Provides-Extra: gap
49
+ Provides-Extra: gitagent
41
50
  Provides-Extra: otel
42
51
  Description-Content-Type: text/markdown
43
52
 
@@ -49,6 +58,8 @@ Description-Content-Type: text/markdown
49
58
 
50
59
  **Drop-in replacement for [`claude-agent-sdk`](https://pypi.org/project/claude-agent-sdk/)** — change the import line, get a proxied telemetry pipeline with PII redaction, configurable OTel export, policy-based tool authorization, and full AgentOS integration for free.
51
60
 
61
+ **New in 0.2.0** — a Python port of the TypeScript [`ComputerAgent`](../ComputerAgent/) harness alongside the drop-in proxy. Same vocabulary (engine / substrate / session-store / identity-loader), `ComputerAgent` + `run_task()` entry points, two built-in engines (`claude-agent-sdk`, `gitagent`). The 0.1.x drop-in surface is untouched — both modes co-exist.
62
+
52
63
  ```diff
53
64
  - from claude_agent_sdk import ClaudeAgentOptions, query
54
65
  - from claude_agent_sdk.types import ResultMessage
@@ -78,11 +89,57 @@ pip install computer-agent-py # core drop-in + OPA policy en
78
89
  pip install 'computer-agent-py[otel]' # + OpenTelemetry sink
79
90
  pip install 'computer-agent-py[agentos]' # + AgentOS Mongo sinks
80
91
  pip install 'computer-agent-py[cedar]' # + Cedar policy engine (in-process)
92
+ pip install 'computer-agent-py[gitagent]' # + gitagent engine (Anthropic + OpenAI dispatch) + GAP loader
93
+ pip install 'computer-agent-py[gap]' # + GAP identity loader only (pyyaml; subset of [gitagent])
81
94
  pip install 'computer-agent-py[all]' # everything
82
95
  ```
83
96
 
84
97
  **Prerequisite** — same as upstream: the `claude` CLI binary on `PATH` and Anthropic / Bedrock credentials in the environment.
85
98
 
99
+ ## Two modes
100
+
101
+ `computer-agent-py` ships two co-existing surfaces. Use whichever fits the call site — they share the same telemetry pipeline and policy authorizer.
102
+
103
+ ### Mode 1 — drop-in proxy (0.1.x, unchanged)
104
+
105
+ The default for callers who want minimum-friction observability over `claude-agent-sdk`. Swap the import line and every `query(...)` / `ClaudeSDKClient(...)` call flows through PII redaction → guardrails → OTel + AgentOS sinks.
106
+
107
+ ```python
108
+ from computeragent import ClaudeAgentOptions, query
109
+
110
+ async for msg in query(prompt="...", options=ClaudeAgentOptions(...)):
111
+ ...
112
+ ```
113
+
114
+ ### Mode 2 — harness (new in 0.2.0)
115
+
116
+ A Python port of the TypeScript [`ComputerAgent`](../ComputerAgent/) stack. Four orthogonal swappable axes — engine / substrate / session-store / identity-loader — plus a `ComputerAgent` class and a `run_task()` convenience.
117
+
118
+ ```python
119
+ from pathlib import Path
120
+ from computeragent import ComputerAgent
121
+
122
+ async with ComputerAgent(
123
+ source=Path.cwd(),
124
+ engine="claude-agent-sdk", # or "gitagent" for OpenAI-compatible endpoints
125
+ runtime="local",
126
+ options={"system_prompt": "You are terse.", "allowed_tools": ["Read", "Glob"]},
127
+ ) as agent:
128
+ result = await agent.chat("List the files in this directory.")
129
+ print(result.messages, result.usage)
130
+ ```
131
+
132
+ Built-ins shipped in 0.2.0:
133
+
134
+ | Axis | Built-ins | Notes |
135
+ |---|---|---|
136
+ | `engine` | `claude-agent-sdk`, `gitagent` | gitagent reads `GITCLAW_MODEL_BASE_URL` + `OPENAI_API_KEY`. MCP-only tools in 0.2.0. |
137
+ | `runtime` (substrate) | `local` | Caller-owned `Path`, or git-clone an `https://` / `git@` `.git` URL into a temp dir. |
138
+ | `session_store` | `memory` | In-process dict. Mongo + SQLite stores in 0.3.0. |
139
+ | `identity_loader` | `passthrough` | Treats inline dicts as manifests. GAP-style loaders in 0.3.0. |
140
+
141
+ Plug-ins register at import time via `register_engine(name, instance)` etc. — third-party engines (e2b substrate, deepagents engine, mongo session store) drop in by implementing the matching Protocol in `computeragent.protocol_types`.
142
+
86
143
  ## Quickstart
87
144
 
88
145
  ```python
@@ -221,7 +278,12 @@ The doc shapes are byte-for-byte compatible with the TypeScript `@open-gitagent/
221
278
 
222
279
  ### Live chat for library-mode agents
223
280
 
224
- When the AgentRegistrySink writes its `agent_registry.source` row, it includes a full inline `IdentitySource` with `files: {agent.yaml, CLAUDE.md}` derived from your `ClaudeAgentOptions`. If a user clicks "New Chat" on the agent in the AgentOS SPA, the harness can clone those files into a sandbox workdir and spawn a live conversation — same UX as hosted (git-sourced) agents. The historical transcript stays available in the Chat tab regardless.
281
+ Two registry shapes for `agent_registry.source`, depending on which mode emitted the event:
282
+
283
+ - **Drop-in proxy (`computeragent.query(...)` / `ClaudeSDKClient`)** writes a `type: "inline"` source with `files: {agent.yaml, CLAUDE.md}` derived from your `ClaudeAgentOptions`. If a user clicks "New Chat" on the agent in the AgentOS SPA, the harness can clone those files into a sandbox workdir and spawn a live conversation — same UX as hosted (git-sourced) agents.
284
+ - **Harness (`ComputerAgent`, new in 0.2.0)** writes a `type: "library"` source. There's no remote `harness-server` to proxy a chat-sandbox spawn to (the agent runs in your Python process), so this shape deliberately fails AgentOS's `hasResolvableSource()` check. The AgentOS UI hides the chat-sandbox button (via the matching `liveChatCapable` derived field on `GET /agents`); historical transcripts remain visible in the Chat tab.
285
+
286
+ Either way, every event still flows through OTel and AgentOS Mongo so the Agents list, Logs tab, and Chat transcript stay populated.
225
287
 
226
288
  ### DocumentDB compatibility
227
289
 
@@ -6,6 +6,8 @@
6
6
 
7
7
  **Drop-in replacement for [`claude-agent-sdk`](https://pypi.org/project/claude-agent-sdk/)** — change the import line, get a proxied telemetry pipeline with PII redaction, configurable OTel export, policy-based tool authorization, and full AgentOS integration for free.
8
8
 
9
+ **New in 0.2.0** — a Python port of the TypeScript [`ComputerAgent`](../ComputerAgent/) harness alongside the drop-in proxy. Same vocabulary (engine / substrate / session-store / identity-loader), `ComputerAgent` + `run_task()` entry points, two built-in engines (`claude-agent-sdk`, `gitagent`). The 0.1.x drop-in surface is untouched — both modes co-exist.
10
+
9
11
  ```diff
10
12
  - from claude_agent_sdk import ClaudeAgentOptions, query
11
13
  - from claude_agent_sdk.types import ResultMessage
@@ -35,11 +37,57 @@ pip install computer-agent-py # core drop-in + OPA policy en
35
37
  pip install 'computer-agent-py[otel]' # + OpenTelemetry sink
36
38
  pip install 'computer-agent-py[agentos]' # + AgentOS Mongo sinks
37
39
  pip install 'computer-agent-py[cedar]' # + Cedar policy engine (in-process)
40
+ pip install 'computer-agent-py[gitagent]' # + gitagent engine (Anthropic + OpenAI dispatch) + GAP loader
41
+ pip install 'computer-agent-py[gap]' # + GAP identity loader only (pyyaml; subset of [gitagent])
38
42
  pip install 'computer-agent-py[all]' # everything
39
43
  ```
40
44
 
41
45
  **Prerequisite** — same as upstream: the `claude` CLI binary on `PATH` and Anthropic / Bedrock credentials in the environment.
42
46
 
47
+ ## Two modes
48
+
49
+ `computer-agent-py` ships two co-existing surfaces. Use whichever fits the call site — they share the same telemetry pipeline and policy authorizer.
50
+
51
+ ### Mode 1 — drop-in proxy (0.1.x, unchanged)
52
+
53
+ The default for callers who want minimum-friction observability over `claude-agent-sdk`. Swap the import line and every `query(...)` / `ClaudeSDKClient(...)` call flows through PII redaction → guardrails → OTel + AgentOS sinks.
54
+
55
+ ```python
56
+ from computeragent import ClaudeAgentOptions, query
57
+
58
+ async for msg in query(prompt="...", options=ClaudeAgentOptions(...)):
59
+ ...
60
+ ```
61
+
62
+ ### Mode 2 — harness (new in 0.2.0)
63
+
64
+ A Python port of the TypeScript [`ComputerAgent`](../ComputerAgent/) stack. Four orthogonal swappable axes — engine / substrate / session-store / identity-loader — plus a `ComputerAgent` class and a `run_task()` convenience.
65
+
66
+ ```python
67
+ from pathlib import Path
68
+ from computeragent import ComputerAgent
69
+
70
+ async with ComputerAgent(
71
+ source=Path.cwd(),
72
+ engine="claude-agent-sdk", # or "gitagent" for OpenAI-compatible endpoints
73
+ runtime="local",
74
+ options={"system_prompt": "You are terse.", "allowed_tools": ["Read", "Glob"]},
75
+ ) as agent:
76
+ result = await agent.chat("List the files in this directory.")
77
+ print(result.messages, result.usage)
78
+ ```
79
+
80
+ Built-ins shipped in 0.2.0:
81
+
82
+ | Axis | Built-ins | Notes |
83
+ |---|---|---|
84
+ | `engine` | `claude-agent-sdk`, `gitagent` | gitagent reads `GITCLAW_MODEL_BASE_URL` + `OPENAI_API_KEY`. MCP-only tools in 0.2.0. |
85
+ | `runtime` (substrate) | `local` | Caller-owned `Path`, or git-clone an `https://` / `git@` `.git` URL into a temp dir. |
86
+ | `session_store` | `memory` | In-process dict. Mongo + SQLite stores in 0.3.0. |
87
+ | `identity_loader` | `passthrough` | Treats inline dicts as manifests. GAP-style loaders in 0.3.0. |
88
+
89
+ Plug-ins register at import time via `register_engine(name, instance)` etc. — third-party engines (e2b substrate, deepagents engine, mongo session store) drop in by implementing the matching Protocol in `computeragent.protocol_types`.
90
+
43
91
  ## Quickstart
44
92
 
45
93
  ```python
@@ -178,7 +226,12 @@ The doc shapes are byte-for-byte compatible with the TypeScript `@open-gitagent/
178
226
 
179
227
  ### Live chat for library-mode agents
180
228
 
181
- When the AgentRegistrySink writes its `agent_registry.source` row, it includes a full inline `IdentitySource` with `files: {agent.yaml, CLAUDE.md}` derived from your `ClaudeAgentOptions`. If a user clicks "New Chat" on the agent in the AgentOS SPA, the harness can clone those files into a sandbox workdir and spawn a live conversation — same UX as hosted (git-sourced) agents. The historical transcript stays available in the Chat tab regardless.
229
+ Two registry shapes for `agent_registry.source`, depending on which mode emitted the event:
230
+
231
+ - **Drop-in proxy (`computeragent.query(...)` / `ClaudeSDKClient`)** writes a `type: "inline"` source with `files: {agent.yaml, CLAUDE.md}` derived from your `ClaudeAgentOptions`. If a user clicks "New Chat" on the agent in the AgentOS SPA, the harness can clone those files into a sandbox workdir and spawn a live conversation — same UX as hosted (git-sourced) agents.
232
+ - **Harness (`ComputerAgent`, new in 0.2.0)** writes a `type: "library"` source. There's no remote `harness-server` to proxy a chat-sandbox spawn to (the agent runs in your Python process), so this shape deliberately fails AgentOS's `hasResolvableSource()` check. The AgentOS UI hides the chat-sandbox button (via the matching `liveChatCapable` derived field on `GET /agents`); historical transcripts remain visible in the Chat tab.
233
+
234
+ Either way, every event still flows through OTel and AgentOS Mongo so the Agents list, Logs tab, and Chat transcript stay populated.
182
235
 
183
236
  ### DocumentDB compatibility
184
237
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "computer-agent-py"
3
- version = "0.1.3"
3
+ version = "0.2.0"
4
4
  description = "Drop-in replacement for claude-agent-sdk that adds a proxied telemetry pipeline (PII redaction + guardrails) with OpenTelemetry and AgentOS sinks."
5
5
  authors = [
6
6
  { name = "Abhi Bhat", email = "abhishek.bhat@lyzr.ai" },
@@ -40,6 +40,14 @@ agentos = [
40
40
  cedar = [
41
41
  "cedarpy>=4,<5",
42
42
  ]
43
+ gitagent = [
44
+ "openai>=1,<2",
45
+ "anthropic>=0.40,<1",
46
+ "pyyaml>=6,<7",
47
+ ]
48
+ gap = [
49
+ "pyyaml>=6,<7",
50
+ ]
43
51
  all = [
44
52
  "opentelemetry-api>=1.27",
45
53
  "opentelemetry-sdk>=1.27",
@@ -47,6 +55,9 @@ all = [
47
55
  "opentelemetry-semantic-conventions>=0.48b0",
48
56
  "motor>=3.5",
49
57
  "cedarpy>=4,<5",
58
+ "openai>=1,<2",
59
+ "anthropic>=0.40,<1",
60
+ "pyyaml>=6,<7",
50
61
  ]
51
62
 
52
63
  [project.urls]
@@ -80,6 +91,9 @@ dev = [
80
91
  "opentelemetry-semantic-conventions>=0.48b0",
81
92
  "motor>=3.5",
82
93
  "cedarpy>=4,<5",
94
+ "openai>=1,<2",
95
+ "anthropic>=0.40,<1",
96
+ "pyyaml>=6,<7",
83
97
  ]
84
98
 
85
99
  [tool.ruff]
@@ -103,10 +117,19 @@ select = [
103
117
  ignore = ["E501"]
104
118
 
105
119
  [tool.ruff.lint.per-file-ignores]
106
- "tests/**" = ["E501", "ASYNC"]
120
+ "tests/**" = ["E501", "ASYNC", "TC001", "TC003"]
107
121
  "examples/**" = ["E501"]
108
122
  "src/computeragent/__init__.py" = ["F401", "F403"]
109
123
  "src/computeragent/types.py" = ["F401", "F403"]
124
+ # 0.2.0 harness layer: protocol value types are referenced from both
125
+ # annotations and runtime code (isinstance, dataclass field defaults).
126
+ # TYPE_CHECKING-only imports would break runtime; ignore the TC nudges.
127
+ "src/computeragent/harness/**" = ["TC001", "TC003", "SIM101"]
128
+ "src/computeragent/engines/**" = ["TC001", "TC003"]
129
+ "src/computeragent/substrates/**" = ["TC003"]
130
+ "src/computeragent/identity/**" = ["TC003"]
131
+ "src/computeragent/session_stores/**" = ["TC001"]
132
+ "src/computeragent/protocol_types/**" = ["TC003"]
110
133
 
111
134
  [tool.mypy]
112
135
  strict = true
@@ -0,0 +1,163 @@
1
+ """``computeragent`` — drop-in replacement for ``claude_agent_sdk``
2
+ PLUS a harness-based agent stack (added in 0.2.0).
3
+
4
+ Every public symbol from ``claude_agent_sdk`` is re-exported here with
5
+ identity preserved (``computeragent.ResultMessage is
6
+ claude_agent_sdk.ResultMessage``). ``query`` and ``ClaudeSDKClient`` are
7
+ wrapped so each message flows through a telemetry pipeline with
8
+ configurable PII redaction / guardrails and pluggable sinks
9
+ (OpenTelemetry, AgentOS registry).
10
+
11
+ Two modes:
12
+
13
+ 1. **Drop-in proxy** (0.1.x, unchanged) — single-import swap::
14
+
15
+ # before
16
+ from claude_agent_sdk import ClaudeAgentOptions, query
17
+ # after
18
+ from computeragent import ClaudeAgentOptions, query
19
+
20
+ 2. **Harness** (new in 0.2.0) — mirror of the TypeScript ComputerAgent
21
+ stack with four orthogonal swappable axes (engine / substrate /
22
+ session-store / identity-loader)::
23
+
24
+ from computeragent import ComputerAgent, run_task
25
+
26
+ async with ComputerAgent(source=Path.cwd()) as agent:
27
+ result = await agent.chat("List the files in this directory")
28
+
29
+ The two modes co-exist. 0.1.x callers see no behaviour change.
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import claude_agent_sdk as _cas
35
+
36
+ # Re-export every public symbol from the upstream package by walking its
37
+ # `__all__`. This is identity-preserving — `computeragent.ResultMessage is
38
+ # claude_agent_sdk.ResultMessage` evaluates True — so user-side
39
+ # `isinstance(msg, ResultMessage)` keeps working regardless of import origin.
40
+ # Skip `__version__` so our own version (set below) wins.
41
+ _upstream_all = [n for n in getattr(_cas, "__all__", []) if n != "__version__"]
42
+ for _name in _upstream_all:
43
+ globals()[_name] = getattr(_cas, _name)
44
+
45
+ __version__ = "0.2.0"
46
+ upstream_version = getattr(_cas, "__version__", "unknown")
47
+
48
+ # Override the two entry points with the proxied versions. Same signatures,
49
+ # same yielded types — but each yielded message also flows through the
50
+ # telemetry pipeline.
51
+ from ._proxy.client import ClaudeSDKClient # noqa: E402
52
+ from ._proxy.query import query # noqa: E402
53
+
54
+ # Harness surface (new in 0.2.0). Importing pulls the registries which
55
+ # register the built-in engines / substrate / session store / identity
56
+ # loader as a side effect — that's intentional so string-label
57
+ # resolution works without an explicit ``register_*`` call.
58
+ from .harness import ( # noqa: E402
59
+ ChatHandle,
60
+ ChatInput,
61
+ ChatResult,
62
+ ComputerAgent,
63
+ HarnessError,
64
+ HarnessProtocolError,
65
+ UnknownEngineError,
66
+ UnknownIdentityLoaderError,
67
+ UnknownSessionStoreError,
68
+ UnknownSubstrateError,
69
+ UsageRollup,
70
+ run_task,
71
+ )
72
+
73
+ # Policy surface (v0.2 — tool-use authorization). Engines themselves
74
+ # (OpaPolicyEngine, CedarPolicyEngine) stay in ``computeragent.policy`` so
75
+ # users opt into Cedar's extra deliberately.
76
+ from .policy import ( # noqa: E402
77
+ PolicyAction,
78
+ PolicyDecision,
79
+ PolicyEngine,
80
+ PolicyInput,
81
+ PolicyPrincipal,
82
+ PolicyResource,
83
+ PolicyResult,
84
+ PolicyToolAuthorizer,
85
+ )
86
+ from .protocol_types import ( # noqa: E402
87
+ AgentManifest,
88
+ EngineCapabilities,
89
+ EngineContext,
90
+ EngineDriver,
91
+ EngineEvent,
92
+ IdentityLoader,
93
+ IdentitySource,
94
+ PermissionDecision,
95
+ PermissionRequest,
96
+ PolicyDecider,
97
+ SessionStore,
98
+ SessionStoreEntry,
99
+ Substrate,
100
+ SubstrateHandle,
101
+ )
102
+
103
+ # Telemetry surface (additions beyond the drop-in mirror).
104
+ from .telemetry import ( # noqa: E402
105
+ GuardrailFilter,
106
+ PiiRedactor,
107
+ Pipeline,
108
+ TelemetryEvent,
109
+ TelemetryMiddleware,
110
+ TelemetrySink,
111
+ configure,
112
+ get_pipeline,
113
+ shutdown,
114
+ )
115
+
116
+ __all__ = sorted({*_upstream_all, "__version__"}) + [
117
+ # telemetry + policy add-ons (alphabetical at the tail so the public
118
+ # symbol list stays scannable when grepped against `claude_agent_sdk.__all__`)
119
+ "GuardrailFilter",
120
+ "PiiRedactor",
121
+ "Pipeline",
122
+ "PolicyAction",
123
+ "PolicyDecision",
124
+ "PolicyEngine",
125
+ "PolicyInput",
126
+ "PolicyPrincipal",
127
+ "PolicyResource",
128
+ "PolicyResult",
129
+ "PolicyToolAuthorizer",
130
+ "TelemetryEvent",
131
+ "TelemetryMiddleware",
132
+ "TelemetrySink",
133
+ "configure",
134
+ "get_pipeline",
135
+ "shutdown",
136
+ # harness layer (0.2.0)
137
+ "AgentManifest",
138
+ "ChatHandle",
139
+ "ChatInput",
140
+ "ChatResult",
141
+ "ComputerAgent",
142
+ "EngineCapabilities",
143
+ "EngineContext",
144
+ "EngineDriver",
145
+ "EngineEvent",
146
+ "HarnessError",
147
+ "HarnessProtocolError",
148
+ "IdentityLoader",
149
+ "IdentitySource",
150
+ "PermissionDecision",
151
+ "PermissionRequest",
152
+ "PolicyDecider",
153
+ "SessionStore",
154
+ "SessionStoreEntry",
155
+ "Substrate",
156
+ "SubstrateHandle",
157
+ "UnknownEngineError",
158
+ "UnknownIdentityLoaderError",
159
+ "UnknownSessionStoreError",
160
+ "UnknownSubstrateError",
161
+ "UsageRollup",
162
+ "run_task",
163
+ ]
@@ -0,0 +1,56 @@
1
+ """Engine registry — string labels → :class:`EngineDriver` instances.
2
+
3
+ Built-ins shipped in 0.2.0:
4
+
5
+ * ``"claude-agent-sdk"`` — wraps ``claude_agent_sdk.query`` directly.
6
+ * ``"gitagent"`` — Python re-implementation of the gitclaw loop over an
7
+ OpenAI-compatible endpoint. Requires the ``[gitagent]`` extra (openai).
8
+ The class is exposed eagerly but the OpenAI client is constructed only
9
+ on first ``start_session`` — registering the engine never fails on
10
+ install-time missing extras.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import logging
16
+
17
+ from ..harness.errors import UnknownEngineError
18
+ from ..protocol_types import EngineDriver
19
+ from .claude_agent import ClaudeAgentEngine
20
+ from .git_agent import GitAgentEngine
21
+
22
+ logger = logging.getLogger("computeragent.engines")
23
+
24
+ _REGISTRY: dict[str, EngineDriver] = {}
25
+
26
+
27
+ def register_engine(name: str, engine: EngineDriver) -> None:
28
+ if not isinstance(engine, EngineDriver):
29
+ raise TypeError(
30
+ f"register_engine({name!r}): instance does not satisfy "
31
+ "the EngineDriver Protocol"
32
+ )
33
+ _REGISTRY[name] = engine
34
+ logger.debug("registered engine %r", name)
35
+
36
+
37
+ def resolve_engine(name: str) -> EngineDriver:
38
+ try:
39
+ return _REGISTRY[name]
40
+ except KeyError as exc:
41
+ raise UnknownEngineError(
42
+ f"unknown engine {name!r}; registered: {sorted(_REGISTRY)!r}"
43
+ ) from exc
44
+
45
+
46
+ # Built-ins.
47
+ register_engine("claude-agent-sdk", ClaudeAgentEngine())
48
+ register_engine("gitagent", GitAgentEngine())
49
+
50
+
51
+ __all__ = [
52
+ "ClaudeAgentEngine",
53
+ "GitAgentEngine",
54
+ "register_engine",
55
+ "resolve_engine",
56
+ ]
@@ -0,0 +1,154 @@
1
+ """``ClaudeAgentEngine`` — adapter that runs the underlying
2
+ ``claude_agent_sdk.query()`` loop inside the harness coordinator.
3
+
4
+ Calls ``claude_agent_sdk.query`` **directly** rather than going through
5
+ ``computeragent._proxy.query``. The proxy emits its own telemetry events
6
+ to the same pipeline the coordinator uses — composing them would double-fire
7
+ every event. Drop-in 0.1.x callers still get the proxy path; harness-mode
8
+ 0.2.0 callers go through this engine and rely on the coordinator's
9
+ telemetry hot path.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ from collections.abc import AsyncIterator
16
+ from contextlib import suppress
17
+ from typing import Any
18
+
19
+ import claude_agent_sdk
20
+
21
+ from ..protocol_types import (
22
+ EngineCapabilities,
23
+ EngineContext,
24
+ EngineEvent,
25
+ )
26
+
27
+ logger = logging.getLogger("computeragent.engines.claude_agent")
28
+
29
+ _CAPABILITIES = EngineCapabilities(
30
+ streaming_input=True,
31
+ partial_messages=True,
32
+ permission_callback=True,
33
+ sessions=True,
34
+ budget=True,
35
+ )
36
+
37
+
38
+ class ClaudeAgentEngine:
39
+ name = "claude-agent-sdk"
40
+ capabilities = _CAPABILITIES
41
+
42
+ async def start_session(
43
+ self, ctx: EngineContext
44
+ ) -> AsyncIterator[EngineEvent]:
45
+ options = _build_options(ctx)
46
+
47
+ # claude-agent-sdk wants prompts as an AsyncIterable of
48
+ # ``{"type": "user", "message": {...}}`` dicts when ``can_use_tool``
49
+ # is set. The harness supplies normalised user messages in
50
+ # ``{"role": "user", "content": ...}`` shape via the queue; wrap
51
+ # them into the SDK's expected envelope here.
52
+ async def _wrapped_prompts():
53
+ async for entry in ctx.user_message_queue:
54
+ if ctx.cancel_event.is_set():
55
+ return
56
+ yield {"type": "user", "message": entry}
57
+
58
+ upstream_kwargs: dict[str, Any] = {
59
+ "prompt": _wrapped_prompts(),
60
+ "options": options,
61
+ }
62
+ try:
63
+ async for msg in claude_agent_sdk.query(**upstream_kwargs):
64
+ if ctx.cancel_event.is_set():
65
+ with suppress(Exception):
66
+ await _maybe_interrupt(msg)
67
+ return
68
+ yield EngineEvent(kind="sdk_message", payload=msg)
69
+ if _is_result_message(msg):
70
+ yield EngineEvent(
71
+ kind="usage_snapshot",
72
+ cost_semantic="cumulative",
73
+ cost_usd=float(getattr(msg, "total_cost_usd", 0.0) or 0.0),
74
+ **_usage_fields(msg),
75
+ )
76
+ except Exception:
77
+ logger.exception("ClaudeAgentEngine.start_session raised")
78
+ raise
79
+
80
+
81
+ def _build_options(ctx: EngineContext) -> Any:
82
+ """Construct a ``ClaudeAgentOptions`` from the harness context."""
83
+ from claude_agent_sdk import ClaudeAgentOptions
84
+
85
+ # The manifest carries raw kwargs the caller passed via ``options=...``.
86
+ # Filter to only those keys ``ClaudeAgentOptions`` accepts so a stray
87
+ # field (e.g. ``model`` typo) raises clearly rather than silently
88
+ # being attached as an attribute.
89
+ accepted = set(ClaudeAgentOptions.__dataclass_fields__.keys())
90
+ payload: dict[str, Any] = {}
91
+ for key, value in (ctx.manifest.options or {}).items():
92
+ if key in accepted:
93
+ payload[key] = value
94
+
95
+ payload.setdefault("cwd", str(ctx.workspace_path))
96
+ if ctx.envs:
97
+ env_existing = dict(payload.get("env") or {})
98
+ env_existing.update(ctx.envs)
99
+ payload["env"] = env_existing
100
+ # Always stream partials so the harness sees mid-turn assistant text.
101
+ payload.setdefault("include_partial_messages", True)
102
+ # Wire policy decider into the can_use_tool slot. ``PolicyToolAuthorizer``
103
+ # already matches the CanUseTool signature, so no glue.
104
+ if ctx.policy_decider is not None:
105
+ payload["can_use_tool"] = ctx.policy_decider
106
+
107
+ options = ClaudeAgentOptions(**payload)
108
+ # Surface harness-supplied agent identity to the proxy's
109
+ # ``_derive_agent_*`` helpers in case telemetry sinks (or downstream
110
+ # exporters) introspect these. Setattr instead of ``ClaudeAgentOptions(**...)``
111
+ # field — the SDK's class doesn't formally declare them but does
112
+ # accept arbitrary attributes via ``__setattr__``.
113
+ with suppress(Exception):
114
+ if ctx.manifest.name:
115
+ options.agent_name = ctx.manifest.name # type: ignore[attr-defined]
116
+ return options
117
+
118
+
119
+ def _is_result_message(msg: Any) -> bool:
120
+ try:
121
+ from claude_agent_sdk.types import ResultMessage # type: ignore
122
+
123
+ return isinstance(msg, ResultMessage)
124
+ except ImportError:
125
+ return False
126
+
127
+
128
+ def _usage_fields(msg: Any) -> dict[str, int | None]:
129
+ usage = getattr(msg, "usage", None)
130
+ if usage is None:
131
+ return {}
132
+ fields: dict[str, int | None] = {}
133
+ for src_key, dest_key in (
134
+ ("input_tokens", "input_tokens"),
135
+ ("output_tokens", "output_tokens"),
136
+ ("cache_creation_input_tokens", "cache_creation_input_tokens"),
137
+ ("cache_read_input_tokens", "cache_read_input_tokens"),
138
+ ):
139
+ value = getattr(usage, src_key, None)
140
+ if value is None and isinstance(usage, dict):
141
+ value = usage.get(src_key)
142
+ if value is not None:
143
+ fields[dest_key] = int(value)
144
+ return fields
145
+
146
+
147
+ async def _maybe_interrupt(msg: Any) -> None:
148
+ # Best-effort cancellation. ``claude_agent_sdk.query`` doesn't expose
149
+ # an interrupt() hook on the iterator itself; the harness setting
150
+ # ``cancel_event`` simply causes us to bail out of the loop.
151
+ return None
152
+
153
+
154
+ __all__ = ["ClaudeAgentEngine"]