computer-agent-py 0.1.1__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.1 → computer_agent_py-0.2.0}/PKG-INFO +64 -2
- {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/README.md +54 -1
- {computer_agent_py-0.1.1 → 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.1.1 → computer_agent_py-0.2.0}/src/computeragent/_proxy/client.py +9 -1
- {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/_proxy/query.py +33 -7
- 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.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/config.py +12 -0
- {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/event.py +8 -0
- {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/sinks/agentos.py +90 -7
- {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/sinks/otel.py +157 -0
- computer_agent_py-0.1.1/src/computeragent/__init__.py +0 -90
- {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/_proxy/__init__.py +0 -0
- {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/policy/__init__.py +0 -0
- {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/policy/authorizer.py +0 -0
- {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/policy/cedar.py +0 -0
- {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/policy/opa.py +0 -0
- {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/policy/types.py +0 -0
- {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/py.typed +0 -0
- {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/__init__.py +0 -0
- {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/middleware/__init__.py +0 -0
- {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/middleware/guardrails.py +0 -0
- {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/middleware/pii.py +0 -0
- {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/pipeline.py +0 -0
- {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/sinks/__init__.py +0 -0
- {computer_agent_py-0.1.1 → computer_agent_py-0.2.0}/src/computeragent/telemetry/sinks/message_archive.py +0 -0
- {computer_agent_py-0.1.1 → 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
|
+
]
|
|
@@ -20,7 +20,7 @@ import claude_agent_sdk
|
|
|
20
20
|
from claude_agent_sdk.types import AssistantMessage, ResultMessage, TextBlock
|
|
21
21
|
|
|
22
22
|
from ..telemetry import TelemetryEvent, get_pipeline, new_session_id
|
|
23
|
-
from .query import _derive_agent_name
|
|
23
|
+
from .query import _derive_agent_description, _derive_agent_name
|
|
24
24
|
|
|
25
25
|
if TYPE_CHECKING:
|
|
26
26
|
from collections.abc import AsyncIterable, AsyncIterator
|
|
@@ -50,6 +50,7 @@ class ClaudeSDKClient:
|
|
|
50
50
|
# placeholder-vs-upstream rationale).
|
|
51
51
|
self._upstream_session_id: str | None = None
|
|
52
52
|
self._agent_name = _derive_agent_name(options)
|
|
53
|
+
self._agent_description = _derive_agent_description(options)
|
|
53
54
|
self._started_at: float | None = None
|
|
54
55
|
self._last_result: ResultMessage | None = None
|
|
55
56
|
self._last_assistant_text: str = ""
|
|
@@ -75,6 +76,7 @@ class ClaudeSDKClient:
|
|
|
75
76
|
TelemetryEvent.session_started(
|
|
76
77
|
session_id=self._session_id,
|
|
77
78
|
agent_name=self._agent_name,
|
|
79
|
+
agent_description=self._agent_description,
|
|
78
80
|
options=self._options,
|
|
79
81
|
prompt=prompt if prompt is not None else "<connect>",
|
|
80
82
|
)
|
|
@@ -122,6 +124,8 @@ class ClaudeSDKClient:
|
|
|
122
124
|
for ev in TelemetryEvent.from_message(
|
|
123
125
|
msg, session_id=self._session_id, agent_name=self._agent_name
|
|
124
126
|
):
|
|
127
|
+
if self._agent_description is not None:
|
|
128
|
+
ev.agent_description = self._agent_description
|
|
125
129
|
await pipeline.emit(ev)
|
|
126
130
|
if isinstance(msg, AssistantMessage):
|
|
127
131
|
for block in getattr(msg, "content", []) or []:
|
|
@@ -145,6 +149,8 @@ class ClaudeSDKClient:
|
|
|
145
149
|
for ev in TelemetryEvent.from_message(
|
|
146
150
|
msg, session_id=self._session_id, agent_name=self._agent_name
|
|
147
151
|
):
|
|
152
|
+
if self._agent_description is not None:
|
|
153
|
+
ev.agent_description = self._agent_description
|
|
148
154
|
await pipeline.emit(ev)
|
|
149
155
|
if isinstance(msg, AssistantMessage):
|
|
150
156
|
for block in getattr(msg, "content", []) or []:
|
|
@@ -203,6 +209,7 @@ class ClaudeSDKClient:
|
|
|
203
209
|
duration_ms=duration_ms,
|
|
204
210
|
last_assistant_text=self._last_assistant_text,
|
|
205
211
|
)
|
|
212
|
+
ev.agent_description = self._agent_description
|
|
206
213
|
if self._upstream_session_id is not None:
|
|
207
214
|
ev.payload["upstream_session_id"] = self._upstream_session_id
|
|
208
215
|
await pipeline.emit(ev)
|
|
@@ -212,6 +219,7 @@ class ClaudeSDKClient:
|
|
|
212
219
|
kind="session_ended",
|
|
213
220
|
session_id=self._session_id,
|
|
214
221
|
agent_name=self._agent_name,
|
|
222
|
+
agent_description=self._agent_description,
|
|
215
223
|
payload={
|
|
216
224
|
"is_error": False,
|
|
217
225
|
"subtype": "no_result",
|
|
@@ -39,6 +39,7 @@ async def query( # noqa: PLR0912 - mirror upstream's tolerant param surface
|
|
|
39
39
|
pipeline = get_pipeline()
|
|
40
40
|
session_id = new_session_id()
|
|
41
41
|
agent_name = _derive_agent_name(options)
|
|
42
|
+
agent_description = _derive_agent_description(options)
|
|
42
43
|
started_at = time.monotonic()
|
|
43
44
|
last_assistant_text = ""
|
|
44
45
|
|
|
@@ -47,6 +48,7 @@ async def query( # noqa: PLR0912 - mirror upstream's tolerant param surface
|
|
|
47
48
|
TelemetryEvent.session_started(
|
|
48
49
|
session_id=session_id,
|
|
49
50
|
agent_name=agent_name,
|
|
51
|
+
agent_description=agent_description,
|
|
50
52
|
options=options,
|
|
51
53
|
prompt=prompt,
|
|
52
54
|
)
|
|
@@ -78,6 +80,11 @@ async def query( # noqa: PLR0912 - mirror upstream's tolerant param surface
|
|
|
78
80
|
for ev in TelemetryEvent.from_message(
|
|
79
81
|
msg, session_id=session_id, agent_name=agent_name
|
|
80
82
|
):
|
|
83
|
+
# Carry the description forward on every event so sinks that
|
|
84
|
+
# group by description (or want to surface it on per-message
|
|
85
|
+
# telemetry) don't have to back-reference session_started.
|
|
86
|
+
if agent_description is not None:
|
|
87
|
+
ev.agent_description = agent_description
|
|
81
88
|
await pipeline.emit(ev)
|
|
82
89
|
|
|
83
90
|
if isinstance(msg, AssistantMessage):
|
|
@@ -100,6 +107,7 @@ async def query( # noqa: PLR0912 - mirror upstream's tolerant param surface
|
|
|
100
107
|
duration_ms=duration_ms,
|
|
101
108
|
last_assistant_text=last_assistant_text,
|
|
102
109
|
)
|
|
110
|
+
ev.agent_description = agent_description
|
|
103
111
|
if upstream_session_id is not None:
|
|
104
112
|
ev.payload["upstream_session_id"] = upstream_session_id
|
|
105
113
|
await pipeline.emit(ev)
|
|
@@ -110,6 +118,7 @@ async def query( # noqa: PLR0912 - mirror upstream's tolerant param surface
|
|
|
110
118
|
kind="session_ended",
|
|
111
119
|
session_id=session_id,
|
|
112
120
|
agent_name=agent_name,
|
|
121
|
+
agent_description=agent_description,
|
|
113
122
|
payload={
|
|
114
123
|
"is_error": False,
|
|
115
124
|
"subtype": "no_result",
|
|
@@ -124,14 +133,14 @@ async def query( # noqa: PLR0912 - mirror upstream's tolerant param surface
|
|
|
124
133
|
except BaseException as exc:
|
|
125
134
|
duration_ms = (time.monotonic() - started_at) * 1000.0
|
|
126
135
|
try:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
duration_ms=duration_ms,
|
|
133
|
-
)
|
|
136
|
+
err_ev = TelemetryEvent.session_ended_error(
|
|
137
|
+
session_id=session_id,
|
|
138
|
+
agent_name=agent_name,
|
|
139
|
+
exc=exc,
|
|
140
|
+
duration_ms=duration_ms,
|
|
134
141
|
)
|
|
142
|
+
err_ev.agent_description = agent_description
|
|
143
|
+
await pipeline.emit(err_ev)
|
|
135
144
|
except Exception: # noqa: BLE001
|
|
136
145
|
logger.debug("error-path telemetry emit failed", exc_info=True)
|
|
137
146
|
raise
|
|
@@ -163,3 +172,20 @@ def _derive_agent_name(options: Any) -> str | None:
|
|
|
163
172
|
if first:
|
|
164
173
|
return first[:80]
|
|
165
174
|
return None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _derive_agent_description(options: Any) -> str | None:
|
|
178
|
+
"""Pick an agent description from options.
|
|
179
|
+
|
|
180
|
+
Mirrors :func:`_derive_agent_name` — the description is purely opt-in.
|
|
181
|
+
Callers set it on options via ``options.agent_description = "..."`` and
|
|
182
|
+
the proxy threads it through to the telemetry pipeline. Unlike the
|
|
183
|
+
name, there's no implicit fallback to system_prompt: a description is
|
|
184
|
+
a human label, not something we want to synthesize from the prompt.
|
|
185
|
+
"""
|
|
186
|
+
if options is None:
|
|
187
|
+
return None
|
|
188
|
+
desc = getattr(options, "agent_description", None)
|
|
189
|
+
if isinstance(desc, str) and desc:
|
|
190
|
+
return desc
|
|
191
|
+
return None
|
|
@@ -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
|
+
]
|