@agentex/agent 0.0.7 → 0.0.8

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.
Files changed (57) hide show
  1. package/README.md +68 -1
  2. package/dist/index.d.ts +2 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/providers/claude/execute.js +8 -8
  7. package/dist/providers/claude/execute.js.map +1 -1
  8. package/dist/providers/claude/index.d.ts.map +1 -1
  9. package/dist/providers/claude/index.js +1 -44
  10. package/dist/providers/claude/index.js.map +1 -1
  11. package/dist/providers/claude/parse.d.ts +30 -6
  12. package/dist/providers/claude/parse.d.ts.map +1 -1
  13. package/dist/providers/claude/parse.js +217 -84
  14. package/dist/providers/claude/parse.js.map +1 -1
  15. package/dist/providers/codex/execute.d.ts.map +1 -1
  16. package/dist/providers/codex/execute.js +38 -26
  17. package/dist/providers/codex/execute.js.map +1 -1
  18. package/dist/providers/codex/index.d.ts.map +1 -1
  19. package/dist/providers/codex/index.js +1 -44
  20. package/dist/providers/codex/index.js.map +1 -1
  21. package/dist/providers/codex/parse.d.ts +18 -6
  22. package/dist/providers/codex/parse.d.ts.map +1 -1
  23. package/dist/providers/codex/parse.js +333 -35
  24. package/dist/providers/codex/parse.js.map +1 -1
  25. package/dist/providers/codex/session.js +16 -2
  26. package/dist/providers/codex/session.js.map +1 -1
  27. package/dist/providers/cursor/index.d.ts.map +1 -1
  28. package/dist/providers/cursor/index.js +1 -10
  29. package/dist/providers/cursor/index.js.map +1 -1
  30. package/dist/providers/cursor/parse.d.ts.map +1 -1
  31. package/dist/providers/cursor/parse.js +33 -12
  32. package/dist/providers/cursor/parse.js.map +1 -1
  33. package/dist/providers/gemini/index.d.ts.map +1 -1
  34. package/dist/providers/gemini/index.js +1 -44
  35. package/dist/providers/gemini/index.js.map +1 -1
  36. package/dist/providers/gemini/parse.d.ts.map +1 -1
  37. package/dist/providers/gemini/parse.js +38 -10
  38. package/dist/providers/gemini/parse.js.map +1 -1
  39. package/dist/providers/openclaw/execute.d.ts.map +1 -1
  40. package/dist/providers/openclaw/execute.js +12 -1
  41. package/dist/providers/openclaw/execute.js.map +1 -1
  42. package/dist/providers/opencode/index.d.ts.map +1 -1
  43. package/dist/providers/opencode/index.js +1 -44
  44. package/dist/providers/opencode/index.js.map +1 -1
  45. package/dist/providers/opencode/parse.d.ts.map +1 -1
  46. package/dist/providers/opencode/parse.js +40 -11
  47. package/dist/providers/opencode/parse.js.map +1 -1
  48. package/dist/providers/pi/index.d.ts.map +1 -1
  49. package/dist/providers/pi/index.js +1 -46
  50. package/dist/providers/pi/index.js.map +1 -1
  51. package/dist/providers/pi/parse.d.ts.map +1 -1
  52. package/dist/providers/pi/parse.js +28 -9
  53. package/dist/providers/pi/parse.js.map +1 -1
  54. package/dist/types.d.ts +167 -19
  55. package/dist/types.d.ts.map +1 -1
  56. package/dist/types.js.map +1 -1
  57. package/package.json +1 -1
@@ -1,54 +1,12 @@
1
1
  import { executeCodexProvider } from "./execute.js";
2
2
  import { createCodexSession } from "./session.js";
3
3
  import { codexSessionCodec } from "./codec.js";
4
- import { findBinary } from "../../utils/binary.js";
5
- import { buildEnv, ensurePathInEnv } from "../../utils/env.js";
6
4
  import { resolveAuthForProvider } from "../../utils/auth.js";
7
- import { ModelCache } from "../../utils/model-cache.js";
8
- import { runChildProcess } from "../../utils/process.js";
9
- const FALLBACK_MODELS = [
10
- { id: "o3", name: "o3", provider: "openai" },
11
- { id: "o4-mini", name: "o4-mini", provider: "openai" },
12
- { id: "codex-mini-latest", name: "Codex Mini", provider: "openai" },
13
- ];
14
- const cache = new ModelCache();
15
- async function fetchModels() {
16
- try {
17
- const resolved = await findBinary("codex");
18
- const env = buildEnv();
19
- ensurePathInEnv(env);
20
- const proc = await runChildProcess({
21
- runId: "list-models",
22
- command: resolved.bin,
23
- args: [...resolved.prefixArgs, "models"],
24
- cwd: process.cwd(),
25
- env,
26
- timeoutSec: 10,
27
- });
28
- if ((proc.exitCode ?? 1) === 0 && proc.stdout.trim()) {
29
- return proc.stdout
30
- .trim()
31
- .split(/\r?\n/)
32
- .filter((line) => line.trim())
33
- .map((line) => {
34
- const id = line.trim();
35
- return { id, name: id, provider: "openai" };
36
- });
37
- }
38
- }
39
- catch {
40
- // Fallback
41
- }
42
- return FALLBACK_MODELS;
43
- }
44
- async function listModels(options) {
45
- return cache.get(options?.cacheTtlMs ?? 0, fetchModels);
46
- }
47
5
  export const codexProvider = {
48
6
  type: "codex",
49
7
  capabilities: {
50
8
  sessions: true,
51
- modelDiscovery: true,
9
+ modelDiscovery: false,
52
10
  quotaProbing: false,
53
11
  mcp: false,
54
12
  skills: true,
@@ -59,6 +17,5 @@ export const codexProvider = {
59
17
  createSession: (ctx) => createCodexSession(ctx),
60
18
  resolveAuth: (ctx) => resolveAuthForProvider("codex", ctx),
61
19
  sessionCodec: codexSessionCodec,
62
- listModels,
63
20
  };
64
21
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/providers/codex/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/providers/codex/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,MAAM,CAAC,MAAM,aAAa,GAAmB;IAC3C,IAAI,EAAE,OAAO;IACb,YAAY,EAAE;QACZ,QAAQ,EAAE,IAAI;QACd,cAAc,EAAE,KAAK;QACrB,YAAY,EAAE,KAAK;QACnB,GAAG,EAAE,KAAK;QACV,MAAM,EAAE,IAAI;QACZ,YAAY,EAAE,IAAI;QAClB,SAAS,EAAE,IAAI;KAChB;IACD,OAAO,EAAE,oBAAoB;IAC7B,aAAa,EAAE,CAAC,GAAmB,EAAyB,EAAE,CAAC,kBAAkB,CAAC,GAAG,CAAC;IACtF,WAAW,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,sBAAsB,CAAC,OAAO,EAAE,GAAG,CAAC;IAC1D,YAAY,EAAE,iBAAiB;CAChC,CAAC"}
@@ -1,19 +1,31 @@
1
- import type { StreamEvent } from "../../types.js";
1
+ import type { StreamEvent, TokenUsage } from "../../types.js";
2
2
  export interface CodexParsedResult {
3
3
  sessionId: string | null;
4
+ /**
5
+ * Codex doesn't emit `model` in its NDJSON output — always null from
6
+ * stdout. Executors should fall back to the requested model.
7
+ */
4
8
  model: string | null;
5
- usage: {
6
- inputTokens: number;
7
- outputTokens: number;
8
- } | null;
9
+ usage: TokenUsage | null;
9
10
  costUsd: number | null;
10
11
  summary: string | null;
11
12
  isError: boolean;
12
13
  errorCode: string | null;
13
14
  errorMessage: string | null;
15
+ /** Final `turn.completed` / `turn.failed` / `error` event verbatim. */
16
+ finalEvent: Record<string, unknown> | null;
14
17
  }
15
18
  export declare function parseCodexJsonl(stdout: string): CodexParsedResult;
16
- export declare function parseCodexStreamLine(line: string): StreamEvent | null;
19
+ /**
20
+ * Parse a single Codex line into a StreamEvent.
21
+ *
22
+ * @param line Raw JSON text.
23
+ * @param sessionId Caller-tracked thread id (NDJSON emits it once on
24
+ * `thread.started` and the executor threads it through).
25
+ * v2 parses it from `params.threadId` directly and
26
+ * ignores this arg.
27
+ */
28
+ export declare function parseCodexStreamLine(line: string, sessionId?: string | null): StreamEvent | null;
17
29
  export declare function stripCodexRolloutNoise(text: string): string;
18
30
  export declare function isCodexAuthRequired(stdout: string, stderr: string): boolean;
19
31
  export declare function isCodexUnknownSessionError(stdout: string, stderr: string): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../../src/providers/codex/parse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5D,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AA6BD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,CAmFjE;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAsGrE;AAKD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS3D;AAID,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAE3E;AAID,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAElF"}
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../../src/providers/codex/parse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,WAAW,EACX,UAAU,EACX,MAAM,gBAAgB,CAAC;AAIxB,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;OAGG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC5C;AAkED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,CA4FjE;AAUD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,SAAS,GAAE,MAAM,GAAG,IAAW,GAC9B,WAAW,GAAG,IAAI,CAWpB;AAyXD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS3D;AAID,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAE3E;AAID,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAElF"}
@@ -1,3 +1,4 @@
1
+ const PROVIDER_TYPE = "codex";
1
2
  function parseJson(line) {
2
3
  try {
3
4
  const parsed = JSON.parse(line);
@@ -13,25 +14,53 @@ function parseJson(line) {
13
14
  function asString(value, fallback) {
14
15
  return typeof value === "string" ? value : fallback;
15
16
  }
17
+ function asNullableString(value) {
18
+ return typeof value === "string" && value.length > 0 ? value : null;
19
+ }
16
20
  function asNumber(value, fallback) {
17
21
  return typeof value === "number" && Number.isFinite(value) ? value : fallback;
18
22
  }
23
+ function asNullableNumber(value) {
24
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
25
+ }
19
26
  function parseObject(value) {
20
27
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
21
28
  return value;
22
29
  }
23
30
  return {};
24
31
  }
32
+ /**
33
+ * Build base fields. For Codex, `eventId` is always null (neither wire
34
+ * format emits a per-line UUID). `sessionId` and `turnId` come from the
35
+ * caller — the v2 parser extracts them from the event's `params`; the
36
+ * NDJSON parser tracks sessionId across lines and leaves turnId null.
37
+ */
38
+ function baseFields(event, sessionId, messageId, turnId) {
39
+ return {
40
+ timestamp: new Date().toISOString(),
41
+ providerType: PROVIDER_TYPE,
42
+ sessionId,
43
+ messageId,
44
+ eventId: null,
45
+ turnId,
46
+ parentToolCallId: null,
47
+ raw: event,
48
+ };
49
+ }
50
+ // ---------------------------------------------------------------------------
51
+ // Run-level parser used by executeCodexProvider to summarize a full stdout.
52
+ // Operates on NDJSON only (executor uses `codex exec --json`).
53
+ // ---------------------------------------------------------------------------
25
54
  export function parseCodexJsonl(stdout) {
26
55
  let sessionId = null;
27
- let model = null;
28
56
  let totalInputTokens = 0;
29
57
  let totalOutputTokens = 0;
58
+ let totalCachedInputTokens = 0;
30
59
  let hasUsage = false;
31
60
  let summary = null;
32
61
  let isError = false;
33
- let errorCode = null;
34
62
  let errorMessage = null;
63
+ let finalEvent = null;
35
64
  for (const rawLine of stdout.split(/\r?\n/)) {
36
65
  const line = rawLine.trim();
37
66
  if (!line)
@@ -41,19 +70,17 @@ export function parseCodexJsonl(stdout) {
41
70
  continue;
42
71
  const type = asString(event["type"], "");
43
72
  if (type === "thread.started") {
44
- sessionId = asString(event["thread_id"], "") || sessionId;
73
+ sessionId = asNullableString(event["thread_id"]) ?? sessionId;
45
74
  continue;
46
75
  }
47
76
  if (type === "item.completed") {
48
77
  const item = parseObject(event["item"]);
49
78
  if (asString(item["type"], "") === "agent_message") {
50
- // Direct text field (Codex 0.30+)
51
79
  const directText = asString(item["text"], "");
52
80
  if (directText) {
53
81
  summary = directText;
54
82
  }
55
83
  else {
56
- // Fallback: content array with output_text blocks
57
84
  const content = Array.isArray(item["content"]) ? item["content"] : [];
58
85
  for (const entry of content) {
59
86
  if (typeof entry !== "object" || entry === null || Array.isArray(entry))
@@ -73,134 +100,405 @@ export function parseCodexJsonl(stdout) {
73
100
  const usage = parseObject(event["usage"]);
74
101
  const inputTokens = asNumber(usage["input_tokens"], 0);
75
102
  const outputTokens = asNumber(usage["output_tokens"], 0);
76
- if (inputTokens > 0 || outputTokens > 0) {
103
+ const cachedInputTokens = asNumber(usage["cached_input_tokens"], 0);
104
+ if (inputTokens > 0 || outputTokens > 0 || cachedInputTokens > 0) {
77
105
  totalInputTokens += inputTokens;
78
106
  totalOutputTokens += outputTokens;
107
+ totalCachedInputTokens += cachedInputTokens;
79
108
  hasUsage = true;
80
109
  }
81
- model = asString(event["model"], "") || model;
110
+ finalEvent = event;
82
111
  continue;
83
112
  }
84
113
  if (type === "turn.failed") {
85
114
  isError = true;
86
- errorMessage = asString(event["message"], "") || asString(event["error"], "") || null;
115
+ errorMessage = asNullableString(event["message"]) ?? asNullableString(event["error"]);
116
+ finalEvent = event;
87
117
  continue;
88
118
  }
89
119
  if (type === "error") {
90
120
  isError = true;
91
- errorMessage = asString(event["message"], "") || null;
121
+ errorMessage = asNullableString(event["message"]);
122
+ finalEvent = event;
92
123
  continue;
93
124
  }
94
125
  }
126
+ const usage = hasUsage ? {
127
+ inputTokens: totalInputTokens,
128
+ outputTokens: totalOutputTokens,
129
+ ...(totalCachedInputTokens > 0 ? { cachedInputTokens: totalCachedInputTokens } : {}),
130
+ } : null;
95
131
  return {
96
132
  sessionId,
97
- model,
98
- usage: hasUsage ? { inputTokens: totalInputTokens, outputTokens: totalOutputTokens } : null,
99
- costUsd: null, // Codex JSONL doesn't report cost
133
+ model: null,
134
+ usage,
135
+ costUsd: null, // Codex doesn't report cost
100
136
  summary,
101
137
  isError,
102
- errorCode,
138
+ errorCode: null,
103
139
  errorMessage,
140
+ finalEvent,
104
141
  };
105
142
  }
106
- export function parseCodexStreamLine(line) {
143
+ // ---------------------------------------------------------------------------
144
+ // Stream-line parser. Auto-detects wire format:
145
+ // - `codex exec --json` NDJSON: `{"type":"...","...": ...}`
146
+ // - `codex --json` v2 JSON-RPC: `{"jsonrpc":"2.0","method":"...","params":{...}}`
147
+ // Both shapes produce the same StreamEvent variants so downstream
148
+ // consumers don't branch on format.
149
+ // ---------------------------------------------------------------------------
150
+ /**
151
+ * Parse a single Codex line into a StreamEvent.
152
+ *
153
+ * @param line Raw JSON text.
154
+ * @param sessionId Caller-tracked thread id (NDJSON emits it once on
155
+ * `thread.started` and the executor threads it through).
156
+ * v2 parses it from `params.threadId` directly and
157
+ * ignores this arg.
158
+ */
159
+ export function parseCodexStreamLine(line, sessionId = null) {
107
160
  const event = parseJson(line);
108
161
  if (!event)
109
162
  return null;
163
+ // v2 JSON-RPC notification: has `method` + `params`.
164
+ if (typeof event["method"] === "string") {
165
+ return parseV2Notification(event);
166
+ }
167
+ // NDJSON legacy format: has `type`.
168
+ return parseNdjsonEvent(event, sessionId);
169
+ }
170
+ // ---------------------------------------------------------------------------
171
+ // v2 JSON-RPC notifications (codex --json app-server mode)
172
+ // ---------------------------------------------------------------------------
173
+ function parseV2Notification(event) {
174
+ const method = asString(event["method"], "");
175
+ const params = parseObject(event["params"]);
176
+ // Extract thread + turn scope. Most notifications carry threadId at the
177
+ // top of params; thread/started nests it under thread.id.
178
+ const thread = parseObject(params["thread"]);
179
+ const threadId = asNullableString(params["threadId"]) ??
180
+ asNullableString(thread["id"]);
181
+ // turn/started + turn/completed nest the turn object; everything else
182
+ // puts turnId at the top of params.
183
+ const turn = parseObject(params["turn"]);
184
+ const turnId = asNullableString(params["turnId"]) ??
185
+ asNullableString(turn["id"]);
186
+ const makeBase = (messageId) => baseFields(event, threadId, messageId, turnId);
187
+ // ---- Thread lifecycle ----
188
+ if (method === "thread/started") {
189
+ return {
190
+ type: "system",
191
+ subtype: "init",
192
+ model: null,
193
+ cwd: asNullableString(thread["cwd"]),
194
+ tools: null,
195
+ permissionMode: null,
196
+ ...makeBase(null),
197
+ };
198
+ }
199
+ // ---- Turn lifecycle ----
200
+ if (method === "turn/started") {
201
+ // Lifecycle marker; items will follow. Skip to reduce noise.
202
+ return null;
203
+ }
204
+ if (method === "turn/completed") {
205
+ return {
206
+ type: "result",
207
+ text: "",
208
+ costUsd: null,
209
+ isError: false,
210
+ stopReason: null,
211
+ terminalReason: asNullableString(turn["status"]),
212
+ numTurns: null,
213
+ durationMs: asNullableNumber(turn["durationMs"]),
214
+ ...makeBase(null),
215
+ };
216
+ }
217
+ if (method === "turn/failed") {
218
+ return {
219
+ type: "result",
220
+ text: asString(params["message"], ""),
221
+ costUsd: null,
222
+ isError: true,
223
+ stopReason: null,
224
+ terminalReason: asNullableString(turn["status"]),
225
+ numTurns: null,
226
+ durationMs: asNullableNumber(turn["durationMs"]),
227
+ ...makeBase(null),
228
+ };
229
+ }
230
+ // ---- Item lifecycle ----
231
+ if (method === "item/started" || method === "item/completed") {
232
+ const item = parseObject(params["item"]);
233
+ const itemType = asString(item["type"], "");
234
+ const itemId = asNullableString(item["id"]) ??
235
+ asNullableString(item["call_id"]);
236
+ const base = makeBase(itemId);
237
+ // Tool starts — emit tool_call on item/started only.
238
+ if (method === "item/started") {
239
+ if (itemType === "command_execution") {
240
+ return {
241
+ type: "tool_call",
242
+ toolCallId: itemId,
243
+ name: "command_execution",
244
+ input: asString(item["command"], ""),
245
+ ...base,
246
+ };
247
+ }
248
+ if (itemType === "function_call") {
249
+ return {
250
+ type: "tool_call",
251
+ toolCallId: itemId,
252
+ name: asString(item["name"], "function_call"),
253
+ input: item["arguments"] ?? item["input"] ?? "",
254
+ ...base,
255
+ };
256
+ }
257
+ // reasoning, agentMessage, userMessage — wait for item/completed.
258
+ return null;
259
+ }
260
+ // item/completed — emit the terminal event for each item type.
261
+ if (itemType === "command_execution") {
262
+ const exitCode = asNullableNumber(item["exit_code"]);
263
+ return {
264
+ type: "tool_result",
265
+ toolCallId: itemId,
266
+ content: asString(item["aggregated_output"], ""),
267
+ isError: exitCode !== null && exitCode !== 0,
268
+ exitCode,
269
+ ...base,
270
+ };
271
+ }
272
+ if (itemType === "function_call") {
273
+ const output = item["output"] ?? item["result"] ?? "";
274
+ return {
275
+ type: "tool_result",
276
+ toolCallId: itemId,
277
+ content: typeof output === "string" ? output : JSON.stringify(output),
278
+ isError: item["status"] === "failed",
279
+ exitCode: null,
280
+ ...base,
281
+ };
282
+ }
283
+ if (itemType === "agentMessage") {
284
+ const directText = asString(item["text"], "");
285
+ if (directText || directText === "") {
286
+ return {
287
+ type: "assistant",
288
+ text: directText,
289
+ ...base,
290
+ };
291
+ }
292
+ }
293
+ if (itemType === "reasoning") {
294
+ // Reasoning content may be empty / encrypted out-of-band — consumers
295
+ // that need the raw payload read it from `raw`.
296
+ const text = asString(item["text"], "") ||
297
+ extractReasoningText(item);
298
+ return {
299
+ type: "thinking",
300
+ text,
301
+ ...base,
302
+ };
303
+ }
304
+ if (itemType === "userMessage") {
305
+ // Consumer persists user input on the write path before calling send().
306
+ // We don't re-emit it here to keep a single source of truth.
307
+ return null;
308
+ }
309
+ // Unknown item type — surface as unknown.
310
+ return {
311
+ type: "unknown",
312
+ subtype: `item/completed:${itemType}`,
313
+ ...base,
314
+ };
315
+ }
316
+ // ---- Streaming deltas: block-level only for v1; skip token deltas. ----
317
+ if (method === "item/agentMessage/delta" || method === "item/reasoning/delta") {
318
+ return null;
319
+ }
320
+ // ---- Rate limits ----
321
+ if (method === "account/rateLimits/updated") {
322
+ const rateLimits = parseObject(params["rateLimits"]);
323
+ const primary = parseObject(rateLimits["primary"]);
324
+ const usedPercent = asNullableNumber(primary["usedPercent"]);
325
+ return {
326
+ type: "rate_limit",
327
+ status: usedPercent !== null && usedPercent >= 100 ? "rejected" : "allowed",
328
+ limitType: asNullableString(rateLimits["limitId"]),
329
+ resetAt: null,
330
+ overageStatus: null,
331
+ isUsingOverage: null,
332
+ ...makeBase(null),
333
+ };
334
+ }
335
+ // ---- Pure telemetry / status ----
336
+ if (method === "thread/tokenUsage/updated" ||
337
+ method === "thread/status/changed" ||
338
+ method === "mcpServer/startupStatus/updated") {
339
+ return null;
340
+ }
341
+ // ---- Forward-compat: unknown method ----
342
+ return {
343
+ type: "unknown",
344
+ subtype: method,
345
+ ...makeBase(null),
346
+ };
347
+ }
348
+ function extractReasoningText(item) {
349
+ // Codex reasoning items carry summary and content arrays. Concatenate
350
+ // any visible text fragments for display; fall back to "" if all empty
351
+ // or encrypted.
352
+ const parts = [];
353
+ for (const key of ["summary", "content"]) {
354
+ const arr = Array.isArray(item[key]) ? item[key] : [];
355
+ for (const entry of arr) {
356
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry))
357
+ continue;
358
+ const block = entry;
359
+ const text = asString(block["text"], "");
360
+ if (text)
361
+ parts.push(text);
362
+ }
363
+ }
364
+ return parts.join("\n").trim();
365
+ }
366
+ // ---------------------------------------------------------------------------
367
+ // NDJSON format (codex exec --json)
368
+ // ---------------------------------------------------------------------------
369
+ function parseNdjsonEvent(event, sessionId) {
110
370
  const type = asString(event["type"], "");
111
- const timestamp = new Date().toISOString();
112
371
  if (type === "thread.started") {
372
+ const threadId = asNullableString(event["thread_id"]);
113
373
  return {
114
374
  type: "system",
115
375
  subtype: "init",
116
- sessionId: asString(event["thread_id"], "") || null,
117
376
  model: null,
118
- timestamp,
377
+ cwd: null,
378
+ tools: null,
379
+ permissionMode: null,
380
+ ...baseFields(event, threadId, null, null),
119
381
  };
120
382
  }
121
383
  if (type === "item.started") {
122
384
  const item = parseObject(event["item"]);
123
385
  const itemType = asString(item["type"], "");
386
+ const itemId = asNullableString(item["id"]) ?? asNullableString(item["call_id"]);
387
+ const base = baseFields(event, sessionId, itemId, null);
124
388
  if (itemType === "command_execution") {
125
389
  return {
126
390
  type: "tool_call",
127
- callId: asString(item["id"], "") || undefined,
391
+ toolCallId: itemId,
128
392
  name: "command_execution",
129
393
  input: asString(item["command"], ""),
130
- timestamp,
394
+ ...base,
131
395
  };
132
396
  }
133
397
  if (itemType === "function_call") {
134
398
  return {
135
399
  type: "tool_call",
136
- callId: asString(item["id"], "") || asString(item["call_id"], "") || undefined,
400
+ toolCallId: itemId,
137
401
  name: asString(item["name"], "function_call"),
138
402
  input: item["arguments"] ?? item["input"] ?? "",
139
- timestamp,
403
+ ...base,
140
404
  };
141
405
  }
142
406
  }
143
407
  if (type === "item.completed") {
144
408
  const item = parseObject(event["item"]);
145
409
  const itemType = asString(item["type"], "");
410
+ const itemId = asNullableString(item["id"]) ?? asNullableString(item["call_id"]);
411
+ const base = baseFields(event, sessionId, itemId, null);
146
412
  if (itemType === "command_execution") {
147
- const exitCode = typeof item["exit_code"] === "number" ? item["exit_code"] : null;
413
+ const exitCode = asNullableNumber(item["exit_code"]);
148
414
  return {
149
415
  type: "tool_result",
150
- toolCallId: asString(item["id"], ""),
416
+ toolCallId: itemId,
151
417
  content: asString(item["aggregated_output"], ""),
152
418
  isError: exitCode !== null && exitCode !== 0,
153
- timestamp,
419
+ exitCode,
420
+ ...base,
154
421
  };
155
422
  }
156
423
  if (itemType === "function_call") {
157
424
  const output = item["output"] ?? item["result"] ?? "";
158
425
  return {
159
426
  type: "tool_result",
160
- toolCallId: asString(item["id"], "") || asString(item["call_id"], ""),
427
+ toolCallId: itemId,
161
428
  content: typeof output === "string" ? output : JSON.stringify(output),
162
429
  isError: item["status"] === "failed",
163
- timestamp,
430
+ exitCode: null,
431
+ ...base,
164
432
  };
165
433
  }
166
434
  if (itemType === "agent_message") {
167
- // Direct text field (Codex 0.30+)
168
435
  const directText = asString(item["text"], "");
169
436
  if (directText) {
170
- return { type: "assistant", text: directText, timestamp };
437
+ return {
438
+ type: "assistant",
439
+ text: directText,
440
+ ...base,
441
+ };
171
442
  }
172
- // Fallback: content array with output_text blocks
173
443
  const content = Array.isArray(item["content"]) ? item["content"] : [];
174
444
  for (const entry of content) {
175
445
  if (typeof entry !== "object" || entry === null || Array.isArray(entry))
176
446
  continue;
177
447
  const block = entry;
178
448
  if (asString(block["type"], "") === "output_text") {
179
- return { type: "assistant", text: asString(block["text"], ""), timestamp };
449
+ return {
450
+ type: "assistant",
451
+ text: asString(block["text"], ""),
452
+ ...base,
453
+ };
180
454
  }
181
455
  }
182
456
  }
457
+ if (itemType === "reasoning") {
458
+ const text = asString(item["text"], "") || extractReasoningText(item);
459
+ return {
460
+ type: "thinking",
461
+ text,
462
+ ...base,
463
+ };
464
+ }
183
465
  }
184
466
  if (type === "turn.completed") {
185
467
  return {
186
468
  type: "result",
187
469
  text: "",
188
- cost: null,
470
+ costUsd: null,
189
471
  isError: false,
190
- timestamp,
472
+ stopReason: null,
473
+ terminalReason: null,
474
+ numTurns: null,
475
+ durationMs: null,
476
+ ...baseFields(event, sessionId, null, null),
191
477
  };
192
478
  }
193
479
  if (type === "error" || type === "turn.failed") {
194
480
  return {
195
481
  type: "result",
196
482
  text: asString(event["message"], ""),
197
- cost: null,
483
+ costUsd: null,
198
484
  isError: true,
199
- timestamp,
485
+ stopReason: null,
486
+ terminalReason: null,
487
+ numTurns: null,
488
+ durationMs: null,
489
+ ...baseFields(event, sessionId, null, null),
200
490
  };
201
491
  }
202
- return null;
492
+ // Forward-compat: surface unknown event types.
493
+ return {
494
+ type: "unknown",
495
+ subtype: type,
496
+ ...baseFields(event, sessionId, null, null),
497
+ };
203
498
  }
499
+ // ---------------------------------------------------------------------------
500
+ // Error detection utilities (unchanged)
501
+ // ---------------------------------------------------------------------------
204
502
  const CODEX_ROLLOUT_NOISE_RE = /^\d{4}-\d{2}-\d{2}T\S+\s+ERROR\s+codex_core::rollout::list:/i;
205
503
  export function stripCodexRolloutNoise(text) {
206
504
  return text