ai-sdk-provider-codex-cli 0.1.0 → 0.3.0

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.
package/dist/index.cjs CHANGED
@@ -13,22 +13,6 @@ var zod = require('zod');
13
13
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
14
14
  // src/codex-cli-provider.ts
15
15
 
16
- // src/extract-json.ts
17
- function extractJson(text) {
18
- const start = text.indexOf("{");
19
- if (start === -1) return text;
20
- let depth = 0;
21
- for (let i = start; i < text.length; i++) {
22
- const ch = text[i];
23
- if (ch === "{") depth++;
24
- else if (ch === "}") {
25
- depth--;
26
- if (depth === 0) return text.slice(start, i + 1);
27
- }
28
- }
29
- return text;
30
- }
31
-
32
16
  // src/logger.ts
33
17
  var defaultLogger = {
34
18
  warn: (m) => console.warn(m),
@@ -108,7 +92,7 @@ function isToolItem(p) {
108
92
  if (out.type === "text" && typeof out.value !== "string") return false;
109
93
  return true;
110
94
  }
111
- function mapMessagesToPrompt(prompt, mode = { type: "regular" }, jsonSchema) {
95
+ function mapMessagesToPrompt(prompt) {
112
96
  const warnings = [];
113
97
  const parts = [];
114
98
  let systemText;
@@ -151,17 +135,6 @@ function mapMessagesToPrompt(prompt, mode = { type: "regular" }, jsonSchema) {
151
135
  let promptText = "";
152
136
  if (systemText) promptText += systemText + "\n\n";
153
137
  promptText += parts.join("\n\n");
154
- if (mode.type === "object-json" && jsonSchema) {
155
- const schemaStr = JSON.stringify(jsonSchema, null, 2);
156
- promptText = `CRITICAL: You MUST respond with ONLY a JSON object. NO other text.
157
- Your response MUST start with { and end with }
158
- The JSON MUST match this EXACT schema:
159
- ${schemaStr}
160
-
161
- Now, based on the following conversation, generate ONLY the JSON object:
162
-
163
- ${promptText}`;
164
- }
165
138
  return { promptText, ...warnings.length ? { warnings } : {} };
166
139
  }
167
140
  function createAPICallError({
@@ -214,7 +187,7 @@ var CodexCliLanguageModel = class {
214
187
  defaultObjectGenerationMode = "json";
215
188
  supportsImageUrls = false;
216
189
  supportedUrls = {};
217
- supportsStructuredOutputs = false;
190
+ supportsStructuredOutputs = true;
218
191
  modelId;
219
192
  settings;
220
193
  logger;
@@ -229,9 +202,19 @@ var CodexCliLanguageModel = class {
229
202
  const warn = validateModelId(this.modelId);
230
203
  if (warn) this.logger.warn(`Codex CLI model: ${warn}`);
231
204
  }
232
- buildArgs(promptText) {
205
+ // Codex JSONL items use `type` for the item discriminator, but some
206
+ // earlier fixtures (and defensive parsing) might still surface `item_type`.
207
+ // This helper returns whichever is present.
208
+ getItemType(item) {
209
+ if (!item) return void 0;
210
+ const data = item;
211
+ const legacy = typeof data.item_type === "string" ? data.item_type : void 0;
212
+ const current = typeof data.type === "string" ? data.type : void 0;
213
+ return legacy ?? current;
214
+ }
215
+ buildArgs(promptText, responseFormat) {
233
216
  const base = resolveCodexPath(this.settings.codexPath, this.settings.allowNpx);
234
- const args = [...base.args, "exec", "--json"];
217
+ const args = [...base.args, "exec", "--experimental-json"];
235
218
  if (this.settings.fullAuto) {
236
219
  args.push("--full-auto");
237
220
  } else if (this.settings.dangerouslyBypassApprovalsAndSandbox) {
@@ -251,6 +234,22 @@ var CodexCliLanguageModel = class {
251
234
  if (this.modelId) {
252
235
  args.push("-m", this.modelId);
253
236
  }
237
+ let schemaPath;
238
+ if (responseFormat?.type === "json" && responseFormat.schema) {
239
+ const schema = typeof responseFormat.schema === "object" ? responseFormat.schema : {};
240
+ const sanitizedSchema = this.sanitizeJsonSchema(schema);
241
+ const hasProperties = Object.keys(sanitizedSchema).length > 0;
242
+ if (hasProperties) {
243
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "codex-schema-"));
244
+ schemaPath = path.join(dir, "schema.json");
245
+ const schemaWithAdditional = {
246
+ ...sanitizedSchema,
247
+ additionalProperties: false
248
+ };
249
+ fs.writeFileSync(schemaPath, JSON.stringify(schemaWithAdditional, null, 2));
250
+ args.push("--output-schema", schemaPath);
251
+ }
252
+ }
254
253
  args.push(promptText);
255
254
  const env = {
256
255
  ...process.env,
@@ -263,7 +262,34 @@ var CodexCliLanguageModel = class {
263
262
  lastMessagePath = path.join(dir, "last-message.txt");
264
263
  }
265
264
  args.push("--output-last-message", lastMessagePath);
266
- return { cmd: base.cmd, args, env, cwd: this.settings.cwd, lastMessagePath };
265
+ return { cmd: base.cmd, args, env, cwd: this.settings.cwd, lastMessagePath, schemaPath };
266
+ }
267
+ sanitizeJsonSchema(value) {
268
+ if (typeof value !== "object" || value === null) {
269
+ return value;
270
+ }
271
+ if (Array.isArray(value)) {
272
+ return value.map((item) => this.sanitizeJsonSchema(item));
273
+ }
274
+ const obj = value;
275
+ const result = {};
276
+ for (const [key, val] of Object.entries(obj)) {
277
+ if (key === "properties" && typeof val === "object" && val !== null && !Array.isArray(val)) {
278
+ const props = val;
279
+ const sanitizedProps = {};
280
+ for (const [propName, propSchema] of Object.entries(props)) {
281
+ sanitizedProps[propName] = this.sanitizeJsonSchema(propSchema);
282
+ }
283
+ result[key] = sanitizedProps;
284
+ continue;
285
+ }
286
+ if (key === "$schema" || key === "$id" || key === "$ref" || key === "$defs" || key === "definitions" || key === "title" || key === "examples" || key === "default" || key === "format" || // OpenAI strict mode doesn't support format
287
+ key === "pattern") {
288
+ continue;
289
+ }
290
+ result[key] = this.sanitizeJsonSchema(val);
291
+ }
292
+ return result;
267
293
  }
268
294
  mapWarnings(options) {
269
295
  const unsupported = [];
@@ -284,13 +310,185 @@ var CodexCliLanguageModel = class {
284
310
  add(options.seed, "seed");
285
311
  return unsupported;
286
312
  }
287
- parseJsonLine(line) {
313
+ parseExperimentalJsonEvent(line) {
288
314
  try {
289
315
  return JSON.parse(line);
290
316
  } catch {
291
317
  return void 0;
292
318
  }
293
319
  }
320
+ extractUsage(evt) {
321
+ const reported = evt.usage;
322
+ if (!reported) return void 0;
323
+ const inputTokens = reported.input_tokens ?? 0;
324
+ const outputTokens = reported.output_tokens ?? 0;
325
+ const cachedInputTokens = reported.cached_input_tokens ?? 0;
326
+ return {
327
+ inputTokens,
328
+ outputTokens,
329
+ // totalTokens should not double-count cached tokens; track cached separately
330
+ totalTokens: inputTokens + outputTokens,
331
+ cachedInputTokens
332
+ };
333
+ }
334
+ getToolName(item) {
335
+ if (!item) return void 0;
336
+ const itemType = this.getItemType(item);
337
+ switch (itemType) {
338
+ case "command_execution":
339
+ return "exec";
340
+ case "file_change":
341
+ return "patch";
342
+ case "mcp_tool_call": {
343
+ const tool = item.tool;
344
+ if (typeof tool === "string" && tool.length > 0) return tool;
345
+ return "mcp_tool";
346
+ }
347
+ case "web_search":
348
+ return "web_search";
349
+ default:
350
+ return void 0;
351
+ }
352
+ }
353
+ buildToolInputPayload(item) {
354
+ if (!item) return void 0;
355
+ const data = item;
356
+ switch (this.getItemType(item)) {
357
+ case "command_execution": {
358
+ const payload = {};
359
+ if (typeof data.command === "string") payload.command = data.command;
360
+ if (typeof data.status === "string") payload.status = data.status;
361
+ if (typeof data.cwd === "string") payload.cwd = data.cwd;
362
+ return Object.keys(payload).length ? payload : void 0;
363
+ }
364
+ case "file_change": {
365
+ const payload = {};
366
+ if (Array.isArray(data.changes)) payload.changes = data.changes;
367
+ if (typeof data.status === "string") payload.status = data.status;
368
+ return Object.keys(payload).length ? payload : void 0;
369
+ }
370
+ case "mcp_tool_call": {
371
+ const payload = {};
372
+ if (typeof data.server === "string") payload.server = data.server;
373
+ if (typeof data.tool === "string") payload.tool = data.tool;
374
+ if (typeof data.status === "string") payload.status = data.status;
375
+ if (data.arguments !== void 0) payload.arguments = data.arguments;
376
+ return Object.keys(payload).length ? payload : void 0;
377
+ }
378
+ case "web_search": {
379
+ const payload = {};
380
+ if (typeof data.query === "string") payload.query = data.query;
381
+ return Object.keys(payload).length ? payload : void 0;
382
+ }
383
+ default:
384
+ return void 0;
385
+ }
386
+ }
387
+ buildToolResultPayload(item) {
388
+ if (!item) return { result: {} };
389
+ const data = item;
390
+ const metadata = {};
391
+ const itemType = this.getItemType(item);
392
+ if (typeof itemType === "string") metadata.itemType = itemType;
393
+ if (typeof item.id === "string") metadata.itemId = item.id;
394
+ if (typeof data.status === "string") metadata.status = data.status;
395
+ const buildResult = (result) => ({
396
+ result,
397
+ metadata: Object.keys(metadata).length ? metadata : void 0
398
+ });
399
+ switch (itemType) {
400
+ case "command_execution": {
401
+ const result = {};
402
+ if (typeof data.command === "string") result.command = data.command;
403
+ if (typeof data.aggregated_output === "string")
404
+ result.aggregatedOutput = data.aggregated_output;
405
+ if (typeof data.exit_code === "number") result.exitCode = data.exit_code;
406
+ if (typeof data.status === "string") result.status = data.status;
407
+ return buildResult(result);
408
+ }
409
+ case "file_change": {
410
+ const result = {};
411
+ if (Array.isArray(data.changes)) result.changes = data.changes;
412
+ if (typeof data.status === "string") result.status = data.status;
413
+ return buildResult(result);
414
+ }
415
+ case "mcp_tool_call": {
416
+ const result = {};
417
+ if (typeof data.server === "string") {
418
+ result.server = data.server;
419
+ metadata.server = data.server;
420
+ }
421
+ if (typeof data.tool === "string") result.tool = data.tool;
422
+ if (typeof data.status === "string") result.status = data.status;
423
+ if (data.result !== void 0) result.result = data.result;
424
+ if (data.error !== void 0) result.error = data.error;
425
+ return buildResult(result);
426
+ }
427
+ case "web_search": {
428
+ const result = {};
429
+ if (typeof data.query === "string") result.query = data.query;
430
+ if (typeof data.status === "string") result.status = data.status;
431
+ return buildResult(result);
432
+ }
433
+ default: {
434
+ const result = { ...data };
435
+ return buildResult(result);
436
+ }
437
+ }
438
+ }
439
+ safeStringify(value) {
440
+ if (value === void 0) return "";
441
+ if (typeof value === "string") return value;
442
+ try {
443
+ return JSON.stringify(value);
444
+ } catch {
445
+ return "";
446
+ }
447
+ }
448
+ emitToolInvocation(controller, toolCallId, toolName, inputPayload) {
449
+ const inputString = this.safeStringify(inputPayload);
450
+ controller.enqueue({ type: "tool-input-start", id: toolCallId, toolName });
451
+ if (inputString) {
452
+ controller.enqueue({ type: "tool-input-delta", id: toolCallId, delta: inputString });
453
+ }
454
+ controller.enqueue({ type: "tool-input-end", id: toolCallId });
455
+ controller.enqueue({
456
+ type: "tool-call",
457
+ toolCallId,
458
+ toolName,
459
+ input: inputString,
460
+ providerExecuted: true
461
+ });
462
+ }
463
+ emitToolResult(controller, toolCallId, toolName, item, resultPayload, metadata) {
464
+ const providerMetadataEntries = {
465
+ ...metadata ?? {}
466
+ };
467
+ const itemType = this.getItemType(item);
468
+ if (itemType && providerMetadataEntries.itemType === void 0) {
469
+ providerMetadataEntries.itemType = itemType;
470
+ }
471
+ if (item.id && providerMetadataEntries.itemId === void 0) {
472
+ providerMetadataEntries.itemId = item.id;
473
+ }
474
+ let isError;
475
+ if (itemType === "command_execution") {
476
+ const data = item;
477
+ const exitCode = typeof data.exit_code === "number" ? data.exit_code : void 0;
478
+ const status = typeof data.status === "string" ? data.status : void 0;
479
+ if (exitCode !== void 0 && exitCode !== 0 || status === "failed") {
480
+ isError = true;
481
+ }
482
+ }
483
+ controller.enqueue({
484
+ type: "tool-result",
485
+ toolCallId,
486
+ toolName,
487
+ result: resultPayload ?? {},
488
+ ...isError ? { isError: true } : {},
489
+ ...Object.keys(providerMetadataEntries).length ? { providerMetadata: { "codex-cli": providerMetadataEntries } } : {}
490
+ });
491
+ }
294
492
  handleSpawnError(err, promptExcerpt) {
295
493
  const e = err && typeof err === "object" ? err : void 0;
296
494
  const message = String((e?.message ?? err) || "Failed to run Codex CLI");
@@ -306,18 +504,17 @@ var CodexCliLanguageModel = class {
306
504
  });
307
505
  }
308
506
  async doGenerate(options) {
309
- const mode = options.responseFormat?.type === "json" ? { type: "object-json" } : { type: "regular" };
310
- const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(
311
- options.prompt,
312
- mode,
313
- options.responseFormat?.type === "json" ? options.responseFormat.schema : void 0
314
- );
507
+ const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
315
508
  const promptExcerpt = promptText.slice(0, 200);
316
509
  const warnings = [
317
510
  ...this.mapWarnings(options),
318
511
  ...mappingWarnings?.map((m) => ({ type: "other", message: m })) || []
319
512
  ];
320
- const { cmd, args, env, cwd, lastMessagePath } = this.buildArgs(promptText);
513
+ const responseFormat = options.responseFormat?.type === "json" ? { type: "json", schema: options.responseFormat.schema } : void 0;
514
+ const { cmd, args, env, cwd, lastMessagePath, schemaPath } = this.buildArgs(
515
+ promptText,
516
+ responseFormat
517
+ );
321
518
  let text = "";
322
519
  const usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
323
520
  const finishReason = "stop";
@@ -334,27 +531,56 @@ var CodexCliLanguageModel = class {
334
531
  try {
335
532
  await new Promise((resolve, reject) => {
336
533
  let stderr = "";
534
+ let turnFailureMessage;
337
535
  child.stderr.on("data", (d) => stderr += String(d));
338
536
  child.stdout.setEncoding("utf8");
339
537
  child.stdout.on("data", (chunk) => {
340
538
  const lines = chunk.split(/\r?\n/).filter(Boolean);
341
539
  for (const line of lines) {
342
- const evt = this.parseJsonLine(line);
343
- if (!evt) continue;
344
- const msg = evt.msg;
345
- const type = msg?.type;
346
- if (type === "session_configured" && msg) {
347
- this.sessionId = msg.session_id;
348
- } else if (type === "task_complete" && msg) {
349
- const last = msg.last_agent_message;
350
- if (typeof last === "string") text = last;
540
+ const event = this.parseExperimentalJsonEvent(line);
541
+ if (!event) continue;
542
+ if (event.type === "thread.started" && typeof event.thread_id === "string") {
543
+ this.sessionId = event.thread_id;
544
+ }
545
+ if (event.type === "session.created" && typeof event.session_id === "string") {
546
+ this.sessionId = event.session_id;
547
+ }
548
+ if (event.type === "turn.completed") {
549
+ const usageEvent = this.extractUsage(event);
550
+ if (usageEvent) {
551
+ usage.inputTokens = usageEvent.inputTokens;
552
+ usage.outputTokens = usageEvent.outputTokens;
553
+ usage.totalTokens = usageEvent.totalTokens;
554
+ }
555
+ }
556
+ if (event.type === "item.completed" && this.getItemType(event.item) === "assistant_message" && typeof event.item?.text === "string") {
557
+ text = event.item.text;
558
+ }
559
+ if (event.type === "turn.failed") {
560
+ const errorText = event.error && typeof event.error.message === "string" && event.error.message || (typeof event.message === "string" ? event.message : void 0);
561
+ turnFailureMessage = errorText ?? turnFailureMessage ?? "Codex turn failed";
562
+ }
563
+ if (event.type === "error") {
564
+ const errorText = typeof event.message === "string" ? event.message : void 0;
565
+ turnFailureMessage = errorText ?? turnFailureMessage ?? "Codex error";
351
566
  }
352
567
  }
353
568
  });
354
569
  child.on("error", (e) => reject(this.handleSpawnError(e, promptExcerpt)));
355
570
  child.on("close", (code) => {
356
- if (code === 0) resolve();
357
- else
571
+ if (code === 0) {
572
+ if (turnFailureMessage) {
573
+ reject(
574
+ createAPICallError({
575
+ message: turnFailureMessage,
576
+ stderr,
577
+ promptExcerpt
578
+ })
579
+ );
580
+ return;
581
+ }
582
+ resolve();
583
+ } else {
358
584
  reject(
359
585
  createAPICallError({
360
586
  message: `Codex CLI exited with code ${code}`,
@@ -363,10 +589,18 @@ var CodexCliLanguageModel = class {
363
589
  promptExcerpt
364
590
  })
365
591
  );
592
+ }
366
593
  });
367
594
  });
368
595
  } finally {
369
596
  if (options.abortSignal && onAbort) options.abortSignal.removeEventListener("abort", onAbort);
597
+ if (schemaPath) {
598
+ try {
599
+ const schemaDir = path.dirname(schemaPath);
600
+ fs.rmSync(schemaDir, { recursive: true, force: true });
601
+ } catch {
602
+ }
603
+ }
370
604
  }
371
605
  if (!text && lastMessagePath) {
372
606
  try {
@@ -381,9 +615,6 @@ var CodexCliLanguageModel = class {
381
615
  } catch {
382
616
  }
383
617
  }
384
- if (options.responseFormat?.type === "json" && text) {
385
- text = extractJson(text);
386
- }
387
618
  const content = [{ type: "text", text }];
388
619
  return {
389
620
  content,
@@ -398,24 +629,86 @@ var CodexCliLanguageModel = class {
398
629
  };
399
630
  }
400
631
  async doStream(options) {
401
- const mode = options.responseFormat?.type === "json" ? { type: "object-json" } : { type: "regular" };
402
- const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(
403
- options.prompt,
404
- mode,
405
- options.responseFormat?.type === "json" ? options.responseFormat.schema : void 0
406
- );
632
+ const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
407
633
  const promptExcerpt = promptText.slice(0, 200);
408
634
  const warnings = [
409
635
  ...this.mapWarnings(options),
410
636
  ...mappingWarnings?.map((m) => ({ type: "other", message: m })) || []
411
637
  ];
412
- const { cmd, args, env, cwd, lastMessagePath } = this.buildArgs(promptText);
638
+ const responseFormat = options.responseFormat?.type === "json" ? { type: "json", schema: options.responseFormat.schema } : void 0;
639
+ const { cmd, args, env, cwd, lastMessagePath, schemaPath } = this.buildArgs(
640
+ promptText,
641
+ responseFormat
642
+ );
413
643
  const stream = new ReadableStream({
414
644
  start: (controller) => {
415
645
  const child = child_process.spawn(cmd, args, { env, cwd, stdio: ["ignore", "pipe", "pipe"] });
416
646
  controller.enqueue({ type: "stream-start", warnings });
417
647
  let stderr = "";
418
648
  let accumulatedText = "";
649
+ const activeTools = /* @__PURE__ */ new Map();
650
+ let responseMetadataSent = false;
651
+ let lastUsage;
652
+ let turnFailureMessage;
653
+ const sendMetadata = (meta = {}) => {
654
+ controller.enqueue({
655
+ type: "response-metadata",
656
+ id: crypto.randomUUID(),
657
+ timestamp: /* @__PURE__ */ new Date(),
658
+ modelId: this.modelId,
659
+ ...Object.keys(meta).length ? { providerMetadata: { "codex-cli": meta } } : {}
660
+ });
661
+ };
662
+ const handleItemEvent = (event) => {
663
+ const item = event.item;
664
+ if (!item) return;
665
+ if (event.type === "item.completed" && this.getItemType(item) === "assistant_message" && typeof item.text === "string") {
666
+ accumulatedText = item.text;
667
+ return;
668
+ }
669
+ const toolName = this.getToolName(item);
670
+ if (!toolName) {
671
+ return;
672
+ }
673
+ const mapKey = typeof item.id === "string" && item.id.length > 0 ? item.id : crypto.randomUUID();
674
+ let toolState = activeTools.get(mapKey);
675
+ const latestInput = this.buildToolInputPayload(item);
676
+ if (!toolState) {
677
+ toolState = {
678
+ toolCallId: mapKey,
679
+ toolName,
680
+ inputPayload: latestInput,
681
+ hasEmittedCall: false
682
+ };
683
+ activeTools.set(mapKey, toolState);
684
+ } else {
685
+ toolState.toolName = toolName;
686
+ if (latestInput !== void 0) {
687
+ toolState.inputPayload = latestInput;
688
+ }
689
+ }
690
+ if (!toolState.hasEmittedCall) {
691
+ this.emitToolInvocation(
692
+ controller,
693
+ toolState.toolCallId,
694
+ toolState.toolName,
695
+ toolState.inputPayload
696
+ );
697
+ toolState.hasEmittedCall = true;
698
+ }
699
+ if (event.type === "item.completed") {
700
+ const { result, metadata } = this.buildToolResultPayload(item);
701
+ this.emitToolResult(
702
+ controller,
703
+ toolState.toolCallId,
704
+ toolState.toolName,
705
+ item,
706
+ result,
707
+ metadata
708
+ );
709
+ activeTools.delete(mapKey);
710
+ }
711
+ };
419
712
  const onAbort = () => {
420
713
  child.kill("SIGTERM");
421
714
  };
@@ -427,37 +720,7 @@ var CodexCliLanguageModel = class {
427
720
  }
428
721
  options.abortSignal.addEventListener("abort", onAbort, { once: true });
429
722
  }
430
- child.stderr.on("data", (d) => stderr += String(d));
431
- child.stdout.setEncoding("utf8");
432
- child.stdout.on("data", (chunk) => {
433
- const lines = chunk.split(/\r?\n/).filter(Boolean);
434
- for (const line of lines) {
435
- const evt = this.parseJsonLine(line);
436
- if (!evt) continue;
437
- const msg = evt.msg;
438
- const type = msg?.type;
439
- if (type === "session_configured" && msg) {
440
- this.sessionId = msg.session_id;
441
- controller.enqueue({
442
- type: "response-metadata",
443
- id: crypto.randomUUID(),
444
- timestamp: /* @__PURE__ */ new Date(),
445
- modelId: this.modelId
446
- });
447
- } else if (type === "task_complete" && msg) {
448
- const last = msg.last_agent_message;
449
- if (typeof last === "string") {
450
- accumulatedText = last;
451
- }
452
- }
453
- }
454
- });
455
- child.on("error", (e) => {
456
- if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
457
- controller.error(this.handleSpawnError(e, promptExcerpt));
458
- });
459
- child.on("close", (code) => {
460
- if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
723
+ const finishStream = (code) => {
461
724
  if (code !== 0) {
462
725
  controller.error(
463
726
  createAPICallError({
@@ -469,6 +732,16 @@ var CodexCliLanguageModel = class {
469
732
  );
470
733
  return;
471
734
  }
735
+ if (turnFailureMessage) {
736
+ controller.error(
737
+ createAPICallError({
738
+ message: turnFailureMessage,
739
+ stderr,
740
+ promptExcerpt
741
+ })
742
+ );
743
+ return;
744
+ }
472
745
  let finalText = accumulatedText;
473
746
  if (!finalText && lastMessagePath) {
474
747
  try {
@@ -482,17 +755,84 @@ var CodexCliLanguageModel = class {
482
755
  }
483
756
  }
484
757
  if (finalText) {
485
- if (options.responseFormat?.type === "json") {
486
- finalText = extractJson(finalText);
487
- }
488
- controller.enqueue({ type: "text-delta", id: crypto.randomUUID(), delta: finalText });
758
+ const textId = crypto.randomUUID();
759
+ controller.enqueue({ type: "text-start", id: textId });
760
+ controller.enqueue({ type: "text-delta", id: textId, delta: finalText });
761
+ controller.enqueue({ type: "text-end", id: textId });
489
762
  }
763
+ const usageSummary = lastUsage ?? { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
490
764
  controller.enqueue({
491
765
  type: "finish",
492
766
  finishReason: "stop",
493
- usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
767
+ usage: usageSummary
494
768
  });
495
769
  controller.close();
770
+ };
771
+ child.stderr.on("data", (d) => stderr += String(d));
772
+ child.stdout.setEncoding("utf8");
773
+ child.stdout.on("data", (chunk) => {
774
+ const lines = chunk.split(/\r?\n/).filter(Boolean);
775
+ for (const line of lines) {
776
+ const event = this.parseExperimentalJsonEvent(line);
777
+ if (!event) continue;
778
+ if (event.type === "thread.started" && typeof event.thread_id === "string") {
779
+ this.sessionId = event.thread_id;
780
+ if (!responseMetadataSent) {
781
+ responseMetadataSent = true;
782
+ sendMetadata();
783
+ }
784
+ continue;
785
+ }
786
+ if (event.type === "session.created" && typeof event.session_id === "string") {
787
+ this.sessionId = event.session_id;
788
+ if (!responseMetadataSent) {
789
+ responseMetadataSent = true;
790
+ sendMetadata();
791
+ }
792
+ continue;
793
+ }
794
+ if (event.type === "turn.completed") {
795
+ const usageEvent = this.extractUsage(event);
796
+ if (usageEvent) {
797
+ lastUsage = usageEvent;
798
+ }
799
+ continue;
800
+ }
801
+ if (event.type === "turn.failed") {
802
+ const errorText = event.error && typeof event.error.message === "string" && event.error.message || (typeof event.message === "string" ? event.message : void 0);
803
+ turnFailureMessage = errorText ?? turnFailureMessage ?? "Codex turn failed";
804
+ sendMetadata({ error: turnFailureMessage });
805
+ continue;
806
+ }
807
+ if (event.type === "error") {
808
+ const errorText = typeof event.message === "string" ? event.message : void 0;
809
+ const effective = errorText ?? "Codex error";
810
+ turnFailureMessage = turnFailureMessage ?? effective;
811
+ sendMetadata({ error: effective });
812
+ continue;
813
+ }
814
+ if (event.type && event.type.startsWith("item.")) {
815
+ handleItemEvent(event);
816
+ }
817
+ }
818
+ });
819
+ const cleanupSchema = () => {
820
+ if (!schemaPath) return;
821
+ try {
822
+ const schemaDir = path.dirname(schemaPath);
823
+ fs.rmSync(schemaDir, { recursive: true, force: true });
824
+ } catch {
825
+ }
826
+ };
827
+ child.on("error", (e) => {
828
+ if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
829
+ cleanupSchema();
830
+ controller.error(this.handleSpawnError(e, promptExcerpt));
831
+ });
832
+ child.on("close", (code) => {
833
+ if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
834
+ cleanupSchema();
835
+ setImmediate(() => finishStream(code));
496
836
  });
497
837
  },
498
838
  cancel: () => {