@proagentstore/cli 0.4.57 → 0.4.59

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.
@@ -29,10 +29,10 @@
29
29
  *
30
30
  * ── THE HONEST GAP ──
31
31
  *
32
- * Only a stream-json engine (Claude Code) produces these. A raw Codex/Grok session emits nothing
33
- * parseable, so it records no acts — the same gap, for the same reason, as engine usage (#267). An
34
- * empty act list therefore means "nothing observed", never "nothing happened", and every consumer
35
- * has to say so rather than render it as an all-clear.
32
+ * Only structured engines produce these. Claude Code emits `tool_use`/`tool_result`; Codex
33
+ * `exec --json` emits `command_execution` events that the adapter normalizes to the same shape.
34
+ * A raw engine emits nothing parseable, so an empty act list means "nothing observed", never
35
+ * "nothing happened", and every consumer has to say so rather than render it as an all-clear.
36
36
  */
37
37
  /** Acts whose consequences reach outside the machine, or cannot be walked back locally. */
38
38
  const IRREVERSIBLE = new Set([
@@ -183,9 +183,9 @@ function wantsPrNumber(act) {
183
183
  * true costs more than it saves. The act still carries `ok: false`, so the record remains honest
184
184
  * that the command failed.
185
185
  *
186
- * Only reachable from the structured stream-json path. A Codex/Grok raw spawn has no
187
- * `tool_use`/`tool_result` framing at all, so its PRs stay unattributed — regexing its transcript
188
- * instead would reintroduce exactly the temporal guess this design refuses.
186
+ * Only reachable from structured adapter events. A raw spawn has no `tool_use`/`tool_result`
187
+ * framing at all, so its PRs stay unattributed — regexing its transcript instead would reintroduce
188
+ * exactly the temporal guess this design refuses.
189
189
  */
190
190
  export function fillTargetFromResult(acts, content) {
191
191
  if (!acts.some(wantsPrNumber))
@@ -0,0 +1,186 @@
1
+ export function engineInvocationModeFromAdapter(mode) {
2
+ return mode === "stream-json" ? "structured" : "raw";
3
+ }
4
+ export function structuredCapableEngine(clientType) {
5
+ return clientType === "claude" || clientType === "codex";
6
+ }
7
+ export function engineInvocationWarning(clientType, mode) {
8
+ if (mode !== "raw" || clientType !== "claude")
9
+ return null;
10
+ return `running raw — structured not available on this machine's ${clientType} CLI`;
11
+ }
12
+ const RESERVED_CLAUDE_FLAGS = new Set(["-p", "--print", "--input-format", "--output-format", "--verbose", "--resume"]);
13
+ /** Structural flags PAGS owns for Claude's stream-json engine. */
14
+ export function buildClaudeArgs(userArgs, resumeId) {
15
+ const args = ["-p", "--input-format", "stream-json", "--output-format", "stream-json", "--verbose"];
16
+ for (let i = 0; i < userArgs.length; i++) {
17
+ const a = userArgs[i];
18
+ if (RESERVED_CLAUDE_FLAGS.has(a)) {
19
+ if (i + 1 < userArgs.length && !userArgs[i + 1].startsWith("-"))
20
+ i++;
21
+ continue;
22
+ }
23
+ args.push(a);
24
+ }
25
+ if (!args.includes("--dangerously-skip-permissions"))
26
+ args.push("--dangerously-skip-permissions");
27
+ if (resumeId)
28
+ args.push("--resume", resumeId);
29
+ return args;
30
+ }
31
+ function record(v) {
32
+ return v && typeof v === "object" && !Array.isArray(v) ? v : null;
33
+ }
34
+ function contentBlocks(ev) {
35
+ const message = record(ev.message);
36
+ const content = Array.isArray(message?.content) ? message.content : [];
37
+ return content.flatMap((block) => {
38
+ const r = record(block);
39
+ return r ? [r] : [];
40
+ });
41
+ }
42
+ function parseClaudeLine(line) {
43
+ let ev;
44
+ try {
45
+ const parsed = JSON.parse(line);
46
+ const parsedRecord = record(parsed);
47
+ if (!parsedRecord)
48
+ return [];
49
+ ev = parsedRecord;
50
+ }
51
+ catch {
52
+ return [];
53
+ }
54
+ const type = typeof ev.type === "string" ? ev.type : "";
55
+ switch (type) {
56
+ case "system": {
57
+ if (ev.subtype === "init" && typeof ev.session_id === "string" && ev.session_id)
58
+ return [{ kind: "session", sessionId: ev.session_id }];
59
+ return [];
60
+ }
61
+ case "assistant":
62
+ return contentBlocks(ev).flatMap((block) => {
63
+ if (block.type === "text" && typeof block.text === "string" && block.text.trim())
64
+ return [{ kind: "assistant_text", text: block.text.trim() }];
65
+ if (block.type !== "tool_use")
66
+ return [];
67
+ const name = String(block.name ?? "tool");
68
+ return [
69
+ {
70
+ kind: "tool_use",
71
+ block,
72
+ id: typeof block.id === "string" ? block.id : "",
73
+ name,
74
+ input: block.input,
75
+ },
76
+ ];
77
+ });
78
+ case "user":
79
+ return contentBlocks(ev).flatMap((block) => {
80
+ if (block.type !== "tool_result")
81
+ return [];
82
+ return [
83
+ {
84
+ kind: "tool_result",
85
+ block,
86
+ toolUseId: typeof block.tool_use_id === "string" ? block.tool_use_id : "",
87
+ content: block.content,
88
+ },
89
+ ];
90
+ });
91
+ case "result": {
92
+ const result = typeof ev.result === "string" ? ev.result : typeof ev.subtype === "string" ? ev.subtype : "failed";
93
+ return [{ kind: "turn_end", raw: ev, isError: ev.is_error === true, result }];
94
+ }
95
+ default:
96
+ return [];
97
+ }
98
+ }
99
+ export const claudeEngineAdapter = {
100
+ mode: "stream-json",
101
+ persistent: true,
102
+ buildLaunchArgs: buildClaudeArgs,
103
+ buildTurnArgs: (userArgs, turnText) => [...userArgs, turnText],
104
+ parseLine: parseClaudeLine,
105
+ };
106
+ function hasFlag(args, flag) {
107
+ return args.some((a) => a === flag || a.startsWith(`${flag}=`));
108
+ }
109
+ function buildCodexExecArgs(userArgs, turnText) {
110
+ const args = [...userArgs];
111
+ if (!hasFlag(args, "--json"))
112
+ args.splice(1, 0, "--json");
113
+ args.push(turnText);
114
+ return args;
115
+ }
116
+ function parseCodexLine(line) {
117
+ let ev;
118
+ try {
119
+ const parsed = JSON.parse(line);
120
+ const parsedRecord = record(parsed);
121
+ if (!parsedRecord)
122
+ return [];
123
+ ev = parsedRecord;
124
+ }
125
+ catch {
126
+ return [];
127
+ }
128
+ const type = typeof ev.type === "string" ? ev.type : "";
129
+ const item = record(ev.item);
130
+ if (type === "thread.started" && typeof ev.thread_id === "string" && ev.thread_id)
131
+ return [{ kind: "session", sessionId: ev.thread_id }];
132
+ if (type === "turn.completed" || type === "turn.failed") {
133
+ const result = typeof ev.error === "string" ? ev.error : typeof ev.message === "string" ? ev.message : type === "turn.failed" ? "failed" : "";
134
+ return [{ kind: "turn_end", raw: ev, isError: type === "turn.failed", result }];
135
+ }
136
+ if (!item)
137
+ return [];
138
+ const itemType = typeof item.type === "string" ? item.type : "";
139
+ if (type === "item.completed" && itemType === "agent_message" && typeof item.text === "string" && item.text.trim()) {
140
+ return [{ kind: "assistant_text", text: item.text.trim() }];
141
+ }
142
+ if (itemType !== "command_execution")
143
+ return [];
144
+ const id = typeof item.id === "string" ? item.id : "";
145
+ const command = typeof item.command === "string" ? item.command : "";
146
+ const block = type === "item.completed"
147
+ ? {
148
+ type: "tool_result",
149
+ tool_use_id: id,
150
+ is_error: item.status === "failed" || (typeof item.exit_code === "number" && item.exit_code !== 0),
151
+ content: typeof item.aggregated_output === "string" ? item.aggregated_output : "",
152
+ }
153
+ : {
154
+ type: "tool_use",
155
+ id,
156
+ name: "Bash",
157
+ input: { command },
158
+ };
159
+ if (type === "item.completed") {
160
+ return [{ kind: "tool_result", block, toolUseId: id, content: block.content }];
161
+ }
162
+ if (type === "item.started" && command)
163
+ return [{ kind: "tool_use", block, id, name: "Bash", input: { command } }];
164
+ return [];
165
+ }
166
+ export const codexEngineAdapter = {
167
+ mode: "stream-json",
168
+ persistent: false,
169
+ buildLaunchArgs: (userArgs) => [...userArgs],
170
+ buildTurnArgs: buildCodexExecArgs,
171
+ parseLine: parseCodexLine,
172
+ };
173
+ export const genericRawEngineAdapter = {
174
+ mode: "raw",
175
+ persistent: false,
176
+ buildLaunchArgs: (userArgs) => [...userArgs],
177
+ buildTurnArgs: (userArgs, turnText) => [...userArgs, turnText],
178
+ parseLine: () => [],
179
+ };
180
+ export function engineAdapterFor(clientType, userArgs = []) {
181
+ if (clientType === "claude")
182
+ return claudeEngineAdapter;
183
+ if (clientType === "codex" && userArgs[0] === "exec" && !["resume", "fork", "review", "help"].includes(userArgs[1] ?? ""))
184
+ return codexEngineAdapter;
185
+ return genericRawEngineAdapter;
186
+ }
@@ -12,9 +12,10 @@
12
12
  * whole ledger that is NOT an estimate, so it is carried through as reported rather than being
13
13
  * re-derived from `ai-pricing.ts` list prices.
14
14
  *
15
- * Raw (non-Claude) engines never reach this module: `HeadlessSession` only parses JSON in
16
- * stream-json mode, so a Codex/Grok session produces no records at all. That is deliberate —
17
- * a zero row would read as "this engine is free", which is a worse lie than a gap.
15
+ * Raw engines never reach this module: `HeadlessSession` only parses usage from structured engine
16
+ * events. Codex `exec --json` reports tokens but no dollar figure on the observed 0.151.0 schema,
17
+ * so those rows carry provider `openai`, model `codex`, and `costUsd: 0` without pretending the CLI
18
+ * reported a price.
18
19
  */
19
20
  const num = (v) => {
20
21
  const n = Number(v);
@@ -55,6 +56,8 @@ export function parseEngineUsage(ev, fallbackId) {
55
56
  if (!ev || typeof ev !== "object")
56
57
  return null;
57
58
  const e = ev;
59
+ if (e.type === "turn.completed")
60
+ return parseCodexUsage(e, fallbackId);
58
61
  if (e.type !== "result")
59
62
  return null;
60
63
  const usage = (e.usage && typeof e.usage === "object" ? e.usage : {});
@@ -68,6 +71,7 @@ export function parseEngineUsage(ev, fallbackId) {
68
71
  const uuid = typeof e.uuid === "string" && e.uuid.trim() ? e.uuid.trim() : "";
69
72
  return {
70
73
  id: uuid || fallbackId,
74
+ provider: "anthropic",
71
75
  model: pickModel(e.modelUsage),
72
76
  inputTokens,
73
77
  outputTokens,
@@ -77,3 +81,23 @@ export function parseEngineUsage(ev, fallbackId) {
77
81
  at: new Date().toISOString(),
78
82
  };
79
83
  }
84
+ function parseCodexUsage(e, fallbackId) {
85
+ const usage = (e.usage && typeof e.usage === "object" ? e.usage : {});
86
+ const inputTokens = num(usage.input_tokens);
87
+ const outputTokens = num(usage.output_tokens);
88
+ const cacheReadTokens = num(usage.cached_input_tokens);
89
+ const cacheWriteTokens = num(usage.cache_write_input_tokens);
90
+ if (!inputTokens && !outputTokens && !cacheReadTokens && !cacheWriteTokens)
91
+ return null;
92
+ return {
93
+ id: fallbackId,
94
+ provider: "openai",
95
+ model: "codex",
96
+ inputTokens,
97
+ outputTokens,
98
+ cacheReadTokens,
99
+ cacheWriteTokens,
100
+ costUsd: 0,
101
+ at: new Date().toISOString(),
102
+ };
103
+ }