zwarm 0.1.0__py3-none-any.whl → 1.0.0__py3-none-any.whl
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.
- zwarm/adapters/claude_code.py +55 -3
- zwarm/adapters/codex_mcp.py +433 -122
- zwarm/adapters/test_codex_mcp.py +26 -26
- zwarm/cli/main.py +464 -3
- zwarm/core/compact.py +312 -0
- zwarm/core/config.py +51 -9
- zwarm/core/environment.py +104 -33
- zwarm/core/models.py +16 -0
- zwarm/core/test_compact.py +266 -0
- zwarm/orchestrator.py +222 -39
- zwarm/prompts/orchestrator.py +128 -146
- zwarm/test_orchestrator_watchers.py +23 -0
- zwarm/tools/delegation.py +23 -4
- zwarm/watchers/builtin.py +90 -4
- zwarm/watchers/manager.py +46 -8
- zwarm/watchers/test_watchers.py +42 -0
- {zwarm-0.1.0.dist-info → zwarm-1.0.0.dist-info}/METADATA +162 -36
- zwarm-1.0.0.dist-info/RECORD +33 -0
- zwarm-0.1.0.dist-info/RECORD +0 -30
- {zwarm-0.1.0.dist-info → zwarm-1.0.0.dist-info}/WHEEL +0 -0
- {zwarm-0.1.0.dist-info → zwarm-1.0.0.dist-info}/entry_points.txt +0 -0
zwarm/adapters/claude_code.py
CHANGED
|
@@ -33,10 +33,46 @@ class ClaudeCodeAdapter(ExecutorAdapter):
|
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
35
|
name = "claude_code"
|
|
36
|
+
DEFAULT_MODEL = "claude-sonnet-4-5-20250514" # Best balance of speed and capability
|
|
36
37
|
|
|
37
38
|
def __init__(self, model: str | None = None):
|
|
38
|
-
self._model = model
|
|
39
|
+
self._model = model or self.DEFAULT_MODEL
|
|
39
40
|
self._sessions: dict[str, str] = {} # session_id -> claude session_id
|
|
41
|
+
# Cumulative token usage for cost tracking
|
|
42
|
+
self._total_usage: dict[str, int] = {
|
|
43
|
+
"input_tokens": 0,
|
|
44
|
+
"output_tokens": 0,
|
|
45
|
+
"cache_creation_input_tokens": 0,
|
|
46
|
+
"cache_read_input_tokens": 0,
|
|
47
|
+
"total_tokens": 0,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
def _accumulate_usage(self, usage: dict[str, Any]) -> None:
|
|
51
|
+
"""Add usage to cumulative totals."""
|
|
52
|
+
if not usage:
|
|
53
|
+
return
|
|
54
|
+
for key in self._total_usage:
|
|
55
|
+
self._total_usage[key] += usage.get(key, 0)
|
|
56
|
+
|
|
57
|
+
def _extract_usage(self, output: dict) -> dict[str, int]:
|
|
58
|
+
"""Extract token usage from claude CLI JSON output."""
|
|
59
|
+
usage = {}
|
|
60
|
+
# Claude CLI may include usage in various formats
|
|
61
|
+
if "usage" in output:
|
|
62
|
+
usage = output["usage"]
|
|
63
|
+
elif "cost_usd" in output:
|
|
64
|
+
# Alternative: estimate from cost if available
|
|
65
|
+
usage["cost_usd"] = output["cost_usd"]
|
|
66
|
+
# Also check for token counts in the output
|
|
67
|
+
for key in ["input_tokens", "output_tokens", "total_tokens"]:
|
|
68
|
+
if key in output:
|
|
69
|
+
usage[key] = output[key]
|
|
70
|
+
return usage
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def total_usage(self) -> dict[str, int]:
|
|
74
|
+
"""Get cumulative token usage across all calls."""
|
|
75
|
+
return self._total_usage.copy()
|
|
40
76
|
|
|
41
77
|
@weave.op()
|
|
42
78
|
async def _call_claude(
|
|
@@ -75,11 +111,14 @@ class ClaudeCodeAdapter(ExecutorAdapter):
|
|
|
75
111
|
|
|
76
112
|
response_text = self._extract_response(result.stdout, result.stderr)
|
|
77
113
|
|
|
78
|
-
# Try to get session ID from JSON output
|
|
114
|
+
# Try to get session ID and usage from JSON output
|
|
79
115
|
session_id = None
|
|
116
|
+
usage = {}
|
|
80
117
|
try:
|
|
81
118
|
output = json.loads(result.stdout)
|
|
82
119
|
session_id = output.get("session_id")
|
|
120
|
+
usage = self._extract_usage(output)
|
|
121
|
+
self._accumulate_usage(usage)
|
|
83
122
|
except (json.JSONDecodeError, TypeError):
|
|
84
123
|
pass
|
|
85
124
|
|
|
@@ -87,6 +126,8 @@ class ClaudeCodeAdapter(ExecutorAdapter):
|
|
|
87
126
|
"response": response_text,
|
|
88
127
|
"session_id": session_id,
|
|
89
128
|
"exit_code": result.returncode,
|
|
129
|
+
"usage": usage,
|
|
130
|
+
"total_usage": self.total_usage,
|
|
90
131
|
}
|
|
91
132
|
|
|
92
133
|
@weave.op()
|
|
@@ -126,16 +167,21 @@ class ClaudeCodeAdapter(ExecutorAdapter):
|
|
|
126
167
|
|
|
127
168
|
response_text = self._extract_response(result.stdout, result.stderr)
|
|
128
169
|
|
|
129
|
-
# Try to get session ID from JSON output
|
|
170
|
+
# Try to get session ID and usage from JSON output
|
|
130
171
|
new_session_id = None
|
|
172
|
+
usage = {}
|
|
131
173
|
try:
|
|
132
174
|
output = json.loads(result.stdout)
|
|
133
175
|
new_session_id = output.get("session_id")
|
|
176
|
+
usage = self._extract_usage(output)
|
|
177
|
+
self._accumulate_usage(usage)
|
|
134
178
|
except (json.JSONDecodeError, TypeError):
|
|
135
179
|
pass
|
|
136
180
|
|
|
137
181
|
return {
|
|
138
182
|
"response": response_text,
|
|
183
|
+
"usage": usage,
|
|
184
|
+
"total_usage": self.total_usage,
|
|
139
185
|
"session_id": new_session_id or session_id,
|
|
140
186
|
"exit_code": result.returncode,
|
|
141
187
|
}
|
|
@@ -175,6 +221,9 @@ class ClaudeCodeAdapter(ExecutorAdapter):
|
|
|
175
221
|
session.add_message("user", task)
|
|
176
222
|
session.add_message("assistant", result["response"])
|
|
177
223
|
|
|
224
|
+
# Track token usage on the session
|
|
225
|
+
session.add_usage(result.get("usage", {}))
|
|
226
|
+
|
|
178
227
|
else:
|
|
179
228
|
# Async mode: run in background
|
|
180
229
|
cmd = ["claude", "-p", "--output-format", "json"]
|
|
@@ -223,6 +272,9 @@ class ClaudeCodeAdapter(ExecutorAdapter):
|
|
|
223
272
|
session.add_message("user", message)
|
|
224
273
|
session.add_message("assistant", response_text)
|
|
225
274
|
|
|
275
|
+
# Track token usage on the session
|
|
276
|
+
session.add_usage(result.get("usage", {}))
|
|
277
|
+
|
|
226
278
|
return response_text
|
|
227
279
|
|
|
228
280
|
async def check_status(
|