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.
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/PKG-INFO +64 -2
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/README.md +54 -1
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/pyproject.toml +25 -2
- computer_agent_py-0.2.0/src/computeragent/__init__.py +163 -0
- computer_agent_py-0.2.0/src/computeragent/engines/__init__.py +56 -0
- computer_agent_py-0.2.0/src/computeragent/engines/claude_agent.py +154 -0
- computer_agent_py-0.2.0/src/computeragent/engines/git_agent.py +485 -0
- computer_agent_py-0.2.0/src/computeragent/harness/__init__.py +303 -0
- computer_agent_py-0.2.0/src/computeragent/harness/coordinator.py +721 -0
- computer_agent_py-0.2.0/src/computeragent/harness/errors.py +37 -0
- computer_agent_py-0.2.0/src/computeragent/harness/usage.py +63 -0
- computer_agent_py-0.2.0/src/computeragent/identity/__init__.py +47 -0
- computer_agent_py-0.2.0/src/computeragent/identity/gap.py +246 -0
- computer_agent_py-0.2.0/src/computeragent/identity/passthrough.py +66 -0
- computer_agent_py-0.2.0/src/computeragent/protocol_types/__init__.py +281 -0
- computer_agent_py-0.2.0/src/computeragent/session_stores/__init__.py +43 -0
- computer_agent_py-0.2.0/src/computeragent/session_stores/memory.py +48 -0
- computer_agent_py-0.2.0/src/computeragent/substrates/__init__.py +45 -0
- computer_agent_py-0.2.0/src/computeragent/substrates/local.py +126 -0
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/config.py +12 -0
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/sinks/agentos.py +71 -5
- computer_agent_py-0.1.3/src/computeragent/__init__.py +0 -90
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/_proxy/__init__.py +0 -0
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/_proxy/client.py +0 -0
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/_proxy/query.py +0 -0
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/policy/__init__.py +0 -0
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/policy/authorizer.py +0 -0
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/policy/cedar.py +0 -0
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/policy/opa.py +0 -0
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/policy/types.py +0 -0
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/py.typed +0 -0
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/__init__.py +0 -0
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/event.py +0 -0
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/middleware/__init__.py +0 -0
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/middleware/guardrails.py +0 -0
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/middleware/pii.py +0 -0
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/pipeline.py +0 -0
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/sinks/__init__.py +0 -0
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/sinks/message_archive.py +0 -0
- {computer_agent_py-0.1.3 → computer_agent_py-0.2.0}/src/computeragent/telemetry/sinks/otel.py +0 -0
- {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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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"]
|