codexcw 0.3.0__tar.gz → 0.4.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.
- {codexcw-0.3.0 → codexcw-0.4.0}/Cargo.lock +3 -3
- {codexcw-0.3.0 → codexcw-0.4.0}/Cargo.toml +2 -2
- {codexcw-0.3.0 → codexcw-0.4.0}/PKG-INFO +1 -1
- {codexcw-0.3.0 → codexcw-0.4.0}/bindings/python/src/lib.rs +9 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/bindings/python/uv.lock +1 -1
- {codexcw-0.3.0 → codexcw-0.4.0}/crates/codexcw/src/claude.rs +96 -5
- {codexcw-0.3.0 → codexcw-0.4.0}/crates/codexcw/src/decoder.rs +85 -2
- {codexcw-0.3.0 → codexcw-0.4.0}/crates/codexcw/src/event.rs +10 -1
- {codexcw-0.3.0 → codexcw-0.4.0}/pyproject.toml +1 -1
- {codexcw-0.3.0 → codexcw-0.4.0}/python/codexcw/_codexcw.pyi +3 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/README.md +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/bindings/python/Cargo.toml +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/bindings/python/README.md +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/bindings/python/tests/fixtures/fake_claude.sh +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/bindings/python/tests/fixtures/fake_codex.sh +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/bindings/python/tests/test_smoke.py +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/crates/codexcw/Cargo.toml +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/crates/codexcw/README.md +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/crates/codexcw/examples/run.rs +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/crates/codexcw/src/account_usage.rs +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/crates/codexcw/src/args.rs +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/crates/codexcw/src/claude_account_usage.rs +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/crates/codexcw/src/error.rs +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/crates/codexcw/src/group.rs +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/crates/codexcw/src/lib.rs +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/crates/codexcw/src/request.rs +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/crates/codexcw/src/runner.rs +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/crates/codexcw/src/session.rs +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/crates/codexcw/src/tail.rs +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/crates/codexcw/tests/account_usage_it.rs +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/crates/codexcw/tests/claude_account_usage_it.rs +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/crates/codexcw/tests/common/mod.rs +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/crates/codexcw/tests/runner_it.rs +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/python/codexcw/__init__.py +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/python/codexcw/aio.py +0 -0
- {codexcw-0.3.0 → codexcw-0.4.0}/python/codexcw/py.typed +0 -0
|
@@ -22,7 +22,7 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
|
22
22
|
|
|
23
23
|
[[package]]
|
|
24
24
|
name = "codexcw"
|
|
25
|
-
version = "0.
|
|
25
|
+
version = "0.4.0"
|
|
26
26
|
dependencies = [
|
|
27
27
|
"serde",
|
|
28
28
|
"serde_json",
|
|
@@ -35,7 +35,7 @@ dependencies = [
|
|
|
35
35
|
|
|
36
36
|
[[package]]
|
|
37
37
|
name = "codexcw-node"
|
|
38
|
-
version = "0.
|
|
38
|
+
version = "0.4.0"
|
|
39
39
|
dependencies = [
|
|
40
40
|
"codexcw",
|
|
41
41
|
"napi",
|
|
@@ -47,7 +47,7 @@ dependencies = [
|
|
|
47
47
|
|
|
48
48
|
[[package]]
|
|
49
49
|
name = "codexcw-py"
|
|
50
|
-
version = "0.
|
|
50
|
+
version = "0.4.0"
|
|
51
51
|
dependencies = [
|
|
52
52
|
"codexcw",
|
|
53
53
|
"pyo3",
|
|
@@ -3,7 +3,7 @@ resolver = "2"
|
|
|
3
3
|
members = ["crates/codexcw", "bindings/python"]
|
|
4
4
|
|
|
5
5
|
[workspace.package]
|
|
6
|
-
version = "0.
|
|
6
|
+
version = "0.4.0"
|
|
7
7
|
edition = "2021"
|
|
8
8
|
rust-version = "1.90"
|
|
9
9
|
license = "CC0-1.0"
|
|
@@ -12,7 +12,7 @@ homepage = "https://github.com/c3-oss/codexcw"
|
|
|
12
12
|
authors = ["c3-oss"]
|
|
13
13
|
|
|
14
14
|
[workspace.dependencies]
|
|
15
|
-
codexcw = { path = "crates/codexcw", version = "0.
|
|
15
|
+
codexcw = { path = "crates/codexcw", version = "0.4.0" }
|
|
16
16
|
tokio = { version = "1", default-features = false, features = [
|
|
17
17
|
"process",
|
|
18
18
|
"io-util",
|
|
@@ -122,6 +122,12 @@ pub struct PyItem {
|
|
|
122
122
|
raw: String,
|
|
123
123
|
#[pyo3(get)]
|
|
124
124
|
changes: Vec<PyFileChange>,
|
|
125
|
+
#[pyo3(get)]
|
|
126
|
+
tool: String,
|
|
127
|
+
#[pyo3(get)]
|
|
128
|
+
sender_thread_id: String,
|
|
129
|
+
#[pyo3(get)]
|
|
130
|
+
receiver_thread_ids: Vec<String>,
|
|
125
131
|
}
|
|
126
132
|
|
|
127
133
|
/// One decoded agent event.
|
|
@@ -628,6 +634,9 @@ fn to_py_item(item: &Item) -> PyItem {
|
|
|
628
634
|
kind: c.kind.clone(),
|
|
629
635
|
})
|
|
630
636
|
.collect(),
|
|
637
|
+
tool: item.tool.clone(),
|
|
638
|
+
sender_thread_id: item.sender_thread_id.clone(),
|
|
639
|
+
receiver_thread_ids: item.receiver_thread_ids.clone(),
|
|
631
640
|
}
|
|
632
641
|
}
|
|
633
642
|
|
|
@@ -341,6 +341,11 @@ impl ClaudeDecoder {
|
|
|
341
341
|
item.changes[0].kind = kind;
|
|
342
342
|
}
|
|
343
343
|
}
|
|
344
|
+
if item.kind == ItemKind::CollabToolCall {
|
|
345
|
+
if let Some(agent_id) = collab_agent_id(wire.tool_use_result.as_deref()) {
|
|
346
|
+
item.receiver_thread_ids = vec![agent_id];
|
|
347
|
+
}
|
|
348
|
+
}
|
|
344
349
|
events.push(item_completed(&base, item));
|
|
345
350
|
}
|
|
346
351
|
if events.is_empty() {
|
|
@@ -403,21 +408,48 @@ impl ClaudeDecoder {
|
|
|
403
408
|
}
|
|
404
409
|
|
|
405
410
|
fn result_usage(wire: &WireEvent) -> Usage {
|
|
411
|
+
// result.usage covers the root agent only; modelUsage and the cost also
|
|
412
|
+
// cover subagents. The full-run total comes from modelUsage when Claude
|
|
413
|
+
// reports it.
|
|
414
|
+
let mut total_tokens = wire.usage.input_tokens
|
|
415
|
+
+ wire.usage.cache_creation_input_tokens
|
|
416
|
+
+ wire.usage.cache_read_input_tokens
|
|
417
|
+
+ wire.usage.output_tokens;
|
|
418
|
+
if !wire.model_usage.is_empty() {
|
|
419
|
+
total_tokens = wire
|
|
420
|
+
.model_usage
|
|
421
|
+
.values()
|
|
422
|
+
.map(|usage| {
|
|
423
|
+
usage.input_tokens
|
|
424
|
+
+ usage.cache_creation_input_tokens
|
|
425
|
+
+ usage.cache_read_input_tokens
|
|
426
|
+
+ usage.output_tokens
|
|
427
|
+
})
|
|
428
|
+
.sum();
|
|
429
|
+
}
|
|
430
|
+
|
|
406
431
|
Usage {
|
|
407
432
|
input_tokens: wire.usage.input_tokens,
|
|
408
433
|
cache_creation_input_tokens: wire.usage.cache_creation_input_tokens,
|
|
409
434
|
cached_input_tokens: wire.usage.cache_read_input_tokens,
|
|
410
435
|
output_tokens: wire.usage.output_tokens,
|
|
411
|
-
total_tokens
|
|
412
|
-
+ wire.usage.cache_creation_input_tokens
|
|
413
|
-
+ wire.usage.cache_read_input_tokens
|
|
414
|
-
+ wire.usage.output_tokens,
|
|
436
|
+
total_tokens,
|
|
415
437
|
total_cost_usd: wire.total_cost_usd,
|
|
416
438
|
model_usage: wire.model_usage.clone(),
|
|
417
439
|
reasoning_output_tokens: 0,
|
|
418
440
|
}
|
|
419
441
|
}
|
|
420
442
|
|
|
443
|
+
fn collab_agent_id(tool_use_result: Option<&RawValue>) -> Option<String> {
|
|
444
|
+
#[derive(serde::Deserialize)]
|
|
445
|
+
struct Wire {
|
|
446
|
+
#[serde(rename = "agentId", default)]
|
|
447
|
+
agent_id: String,
|
|
448
|
+
}
|
|
449
|
+
let wire: Wire = serde_json::from_str(tool_use_result?.get()).ok()?;
|
|
450
|
+
(!wire.agent_id.is_empty()).then_some(wire.agent_id)
|
|
451
|
+
}
|
|
452
|
+
|
|
421
453
|
fn tool_item(block: &WireBlock, raw_block: &str) -> Item {
|
|
422
454
|
let input: WireToolInput = block
|
|
423
455
|
.input
|
|
@@ -455,7 +487,12 @@ fn tool_item(block: &WireBlock, raw_block: &str) -> Item {
|
|
|
455
487
|
}];
|
|
456
488
|
}
|
|
457
489
|
"WebSearch" => item.kind = ItemKind::WebSearch,
|
|
458
|
-
"Task"
|
|
490
|
+
// "Task" is the legacy name of the subagent tool; current Claude
|
|
491
|
+
// Code CLIs call it "Agent".
|
|
492
|
+
"Task" | "Agent" => {
|
|
493
|
+
item.kind = ItemKind::CollabToolCall;
|
|
494
|
+
item.tool = block.name.clone();
|
|
495
|
+
}
|
|
459
496
|
"TodoWrite" => item.kind = ItemKind::PlanUpdate,
|
|
460
497
|
name if name.starts_with("mcp__") => item.kind = ItemKind::McpToolCall,
|
|
461
498
|
_ => item.kind = ItemKind::ToolCall,
|
|
@@ -716,11 +753,65 @@ mod tests {
|
|
|
716
753
|
.unwrap();
|
|
717
754
|
let task = started[0].item_started().unwrap();
|
|
718
755
|
assert_eq!(task.kind, ItemKind::CollabToolCall);
|
|
756
|
+
assert_eq!(task.tool, "Task");
|
|
719
757
|
assert!(task.raw.contains("subagent_type"));
|
|
720
758
|
let todo = started[1].item_started().unwrap();
|
|
721
759
|
assert_eq!(todo.kind, ItemKind::PlanUpdate);
|
|
722
760
|
}
|
|
723
761
|
|
|
762
|
+
#[test]
|
|
763
|
+
fn agent_tool_maps_to_collab_with_receiver_id() {
|
|
764
|
+
// Current Claude Code CLIs call the subagent tool "Agent"; "Task" is
|
|
765
|
+
// the legacy name. This mirrors a real 2.1.x payload.
|
|
766
|
+
let mut decoder = ClaudeDecoder::default();
|
|
767
|
+
let started = decoder
|
|
768
|
+
.decode(
|
|
769
|
+
br#"{"type":"assistant","message":{"id":"msg_1","content":[{"type":"tool_use","id":"toolu_1","name":"Agent","input":{"subagent_type":"Explore","prompt":"reply SUBAGENT_CHILD_OK"}}]},"session_id":"s"}"#,
|
|
770
|
+
"run-x",
|
|
771
|
+
"s",
|
|
772
|
+
SystemTime::UNIX_EPOCH,
|
|
773
|
+
)
|
|
774
|
+
.unwrap();
|
|
775
|
+
let item = started[0].item_started().unwrap();
|
|
776
|
+
assert_eq!(item.kind, ItemKind::CollabToolCall);
|
|
777
|
+
assert_eq!(item.tool, "Agent");
|
|
778
|
+
|
|
779
|
+
let completed = decoder
|
|
780
|
+
.decode(
|
|
781
|
+
br#"{"type":"user","message":{"content":[{"type":"tool_result","tool_use_id":"toolu_1","content":[{"type":"text","text":"SUBAGENT_CHILD_OK"}]}]},"tool_use_result":{"status":"completed","agentType":"Explore","agentId":"agent-123","resolvedModel":"claude-sonnet-5","totalTokens":12460},"session_id":"s"}"#,
|
|
782
|
+
"run-x",
|
|
783
|
+
"s",
|
|
784
|
+
SystemTime::UNIX_EPOCH,
|
|
785
|
+
)
|
|
786
|
+
.unwrap();
|
|
787
|
+
let item = completed[0].item_completed().unwrap();
|
|
788
|
+
assert_eq!(item.kind, ItemKind::CollabToolCall);
|
|
789
|
+
assert_eq!(item.tool, "Agent");
|
|
790
|
+
assert_eq!(item.status, "completed");
|
|
791
|
+
assert_eq!(item.receiver_thread_ids, vec!["agent-123".to_string()]);
|
|
792
|
+
assert_eq!(item.aggregated_output, "SUBAGENT_CHILD_OK");
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
#[test]
|
|
796
|
+
fn result_total_tokens_come_from_model_usage_when_present() {
|
|
797
|
+
let mut decoder = ClaudeDecoder::default();
|
|
798
|
+
let events = decoder
|
|
799
|
+
.decode(
|
|
800
|
+
br#"{"type":"result","is_error":false,"result":"ok","session_id":"s","total_cost_usd":0.5,"usage":{"input_tokens":100,"cache_creation_input_tokens":200,"cache_read_input_tokens":300,"output_tokens":50},"modelUsage":{"claude-sonnet-5":{"inputTokens":100,"outputTokens":50,"cacheReadInputTokens":300,"cacheCreationInputTokens":200},"claude-haiku-4-5-20251001":{"inputTokens":8000,"outputTokens":460,"cacheReadInputTokens":3000,"cacheCreationInputTokens":1000}}}"#,
|
|
801
|
+
"run-x",
|
|
802
|
+
"s",
|
|
803
|
+
SystemTime::UNIX_EPOCH,
|
|
804
|
+
)
|
|
805
|
+
.unwrap();
|
|
806
|
+
let EventPayload::TurnCompleted { usage } = &events.last().unwrap().payload else {
|
|
807
|
+
panic!("expected turn.completed");
|
|
808
|
+
};
|
|
809
|
+
assert_eq!(usage.input_tokens, 100);
|
|
810
|
+
// The root result.usage sums to 650; the subagent's 12460 tokens only
|
|
811
|
+
// appear in modelUsage, and the full-run total must include them.
|
|
812
|
+
assert_eq!(usage.total_tokens, 650 + 12460);
|
|
813
|
+
}
|
|
814
|
+
|
|
724
815
|
#[test]
|
|
725
816
|
fn bash_failure_extracts_exit_code() {
|
|
726
817
|
let mut decoder = ClaudeDecoder::default();
|
|
@@ -46,6 +46,12 @@ struct WireItem {
|
|
|
46
46
|
exit_code: Option<i32>,
|
|
47
47
|
#[serde(default)]
|
|
48
48
|
changes: Vec<FileChange>,
|
|
49
|
+
#[serde(default)]
|
|
50
|
+
tool: String,
|
|
51
|
+
#[serde(default)]
|
|
52
|
+
sender_thread_id: String,
|
|
53
|
+
#[serde(default)]
|
|
54
|
+
receiver_thread_ids: Vec<String>,
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
/// Decodes one JSONL line into an [`Event`].
|
|
@@ -83,10 +89,12 @@ pub(crate) fn decode_event(
|
|
|
83
89
|
}
|
|
84
90
|
}
|
|
85
91
|
EventKind::TurnStarted => EventPayload::TurnStarted,
|
|
86
|
-
EventKind::TurnCompleted => EventPayload::TurnCompleted {
|
|
92
|
+
EventKind::TurnCompleted => EventPayload::TurnCompleted {
|
|
93
|
+
usage: normalize_codex_usage(wire.usage),
|
|
94
|
+
},
|
|
87
95
|
EventKind::TurnFailed => EventPayload::TurnFailed {
|
|
88
96
|
error: decode_event_error(wire.error.as_deref()),
|
|
89
|
-
usage: wire.usage,
|
|
97
|
+
usage: normalize_codex_usage(wire.usage),
|
|
90
98
|
},
|
|
91
99
|
EventKind::ItemStarted => EventPayload::ItemStarted(decode_item(wire.item.as_deref())?),
|
|
92
100
|
EventKind::ItemCompleted => EventPayload::ItemCompleted(decode_item(wire.item.as_deref())?),
|
|
@@ -129,9 +137,22 @@ fn decode_item(raw: Option<&RawValue>) -> Result<Item, String> {
|
|
|
129
137
|
aggregated_output: wire.aggregated_output,
|
|
130
138
|
exit_code: wire.exit_code,
|
|
131
139
|
changes: wire.changes,
|
|
140
|
+
tool: wire.tool,
|
|
141
|
+
sender_thread_id: wire.sender_thread_id,
|
|
142
|
+
receiver_thread_ids: wire.receiver_thread_ids,
|
|
132
143
|
})
|
|
133
144
|
}
|
|
134
145
|
|
|
146
|
+
/// Derives the total when Codex omits `total_tokens`. `cached_input_tokens`
|
|
147
|
+
/// is a subset of `input_tokens` on the codex wire, so the derived total is
|
|
148
|
+
/// input plus output; an explicit total is preserved.
|
|
149
|
+
fn normalize_codex_usage(mut usage: Usage) -> Usage {
|
|
150
|
+
if usage.total_tokens == 0 {
|
|
151
|
+
usage.total_tokens = usage.input_tokens + usage.output_tokens;
|
|
152
|
+
}
|
|
153
|
+
usage
|
|
154
|
+
}
|
|
155
|
+
|
|
135
156
|
fn decode_event_error(raw: Option<&RawValue>) -> ErrorPayload {
|
|
136
157
|
let Some(raw) = raw else {
|
|
137
158
|
return ErrorPayload::default();
|
|
@@ -216,6 +237,68 @@ mod tests {
|
|
|
216
237
|
assert_eq!(item.kind, ItemKind::CollabToolCall);
|
|
217
238
|
assert_eq!(item.status, "in_progress");
|
|
218
239
|
assert!(item.raw.contains("\"tool\":\"wait\""));
|
|
240
|
+
assert_eq!(item.tool, "wait");
|
|
241
|
+
assert_eq!(item.sender_thread_id, "t-parent");
|
|
242
|
+
assert!(item.receiver_thread_ids.is_empty());
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
#[test]
|
|
246
|
+
fn collab_tool_call_exposes_receiver_thread_ids() {
|
|
247
|
+
let event = decode_event(
|
|
248
|
+
br#"{"type":"item.started","item":{"id":"i0","type":"collab_tool_call","tool":"spawn_agent","sender_thread_id":"t-parent","receiver_thread_ids":["t-child"],"agents_states":{},"status":"in_progress"}}"#,
|
|
249
|
+
"run-x",
|
|
250
|
+
"t-parent",
|
|
251
|
+
SystemTime::UNIX_EPOCH,
|
|
252
|
+
)
|
|
253
|
+
.unwrap();
|
|
254
|
+
let item = event.item_started().unwrap();
|
|
255
|
+
assert_eq!(item.tool, "spawn_agent");
|
|
256
|
+
assert_eq!(item.receiver_thread_ids, vec!["t-child".to_string()]);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
#[test]
|
|
260
|
+
fn turn_completed_total_tokens_derived_when_omitted() {
|
|
261
|
+
// cached_input_tokens is a subset of input_tokens on the codex wire
|
|
262
|
+
// and must not be counted twice.
|
|
263
|
+
let event = decode_event(
|
|
264
|
+
br#"{"type":"turn.completed","usage":{"input_tokens":12966,"cached_input_tokens":8960,"output_tokens":6,"reasoning_output_tokens":0}}"#,
|
|
265
|
+
"run-x",
|
|
266
|
+
"t-1",
|
|
267
|
+
SystemTime::UNIX_EPOCH,
|
|
268
|
+
)
|
|
269
|
+
.unwrap();
|
|
270
|
+
let EventPayload::TurnCompleted { usage } = &event.payload else {
|
|
271
|
+
panic!("expected turn.completed");
|
|
272
|
+
};
|
|
273
|
+
assert_eq!(usage.total_tokens, 12972);
|
|
274
|
+
|
|
275
|
+
let explicit = decode_event(
|
|
276
|
+
br#"{"type":"turn.completed","usage":{"input_tokens":10,"output_tokens":3,"total_tokens":999}}"#,
|
|
277
|
+
"run-x",
|
|
278
|
+
"t-1",
|
|
279
|
+
SystemTime::UNIX_EPOCH,
|
|
280
|
+
)
|
|
281
|
+
.unwrap();
|
|
282
|
+
let EventPayload::TurnCompleted { usage } = &explicit.payload else {
|
|
283
|
+
panic!("expected turn.completed");
|
|
284
|
+
};
|
|
285
|
+
assert_eq!(usage.total_tokens, 999);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
#[test]
|
|
289
|
+
fn turn_failed_total_tokens_derived_when_omitted() {
|
|
290
|
+
let event = decode_event(
|
|
291
|
+
br#"{"type":"turn.failed","error":{"message":"boom"},"usage":{"input_tokens":12966,"cached_input_tokens":8960,"output_tokens":6}}"#,
|
|
292
|
+
"run-x",
|
|
293
|
+
"t-1",
|
|
294
|
+
SystemTime::UNIX_EPOCH,
|
|
295
|
+
)
|
|
296
|
+
.unwrap();
|
|
297
|
+
let EventPayload::TurnFailed { error, usage } = &event.payload else {
|
|
298
|
+
panic!("expected turn.failed");
|
|
299
|
+
};
|
|
300
|
+
assert_eq!(error.message, "boom");
|
|
301
|
+
assert_eq!(usage.total_tokens, 12972);
|
|
219
302
|
}
|
|
220
303
|
|
|
221
304
|
#[test]
|
|
@@ -151,7 +151,9 @@ pub struct Usage {
|
|
|
151
151
|
/// Number of reasoning output tokens.
|
|
152
152
|
#[serde(default)]
|
|
153
153
|
pub reasoning_output_tokens: i64,
|
|
154
|
-
/// Total
|
|
154
|
+
/// Total token count for the full run, subagents included. When the
|
|
155
|
+
/// agent omits an explicit total it is derived: input plus output tokens
|
|
156
|
+
/// for Codex, and the per-model sums when Claude reports model usage.
|
|
155
157
|
#[serde(default)]
|
|
156
158
|
pub total_tokens: i64,
|
|
157
159
|
/// Total Claude API cost in US dollars.
|
|
@@ -226,6 +228,13 @@ pub struct Item {
|
|
|
226
228
|
pub exit_code: Option<i32>,
|
|
227
229
|
/// File edits for `file_change` items.
|
|
228
230
|
pub changes: Vec<FileChange>,
|
|
231
|
+
/// Collab operation (`spawn_agent`, `wait`, ...) for `collab_tool_call`
|
|
232
|
+
/// items.
|
|
233
|
+
pub tool: String,
|
|
234
|
+
/// Thread that issued a `collab_tool_call` item.
|
|
235
|
+
pub sender_thread_id: String,
|
|
236
|
+
/// Agent threads targeted by a `collab_tool_call` item.
|
|
237
|
+
pub receiver_thread_ids: Vec<String>,
|
|
229
238
|
}
|
|
230
239
|
|
|
231
240
|
impl Default for ItemKind {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|