ai-sdk-provider-codex-cli 0.2.0 → 0.4.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
@@ -7,8 +7,8 @@ var module$1 = require('module');
7
7
  var fs = require('fs');
8
8
  var os = require('os');
9
9
  var path = require('path');
10
- var providerUtils = require('@ai-sdk/provider-utils');
11
10
  var zod = require('zod');
11
+ var providerUtils = require('@ai-sdk/provider-utils');
12
12
 
13
13
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
14
14
  // src/codex-cli-provider.ts
@@ -41,7 +41,29 @@ var settingsSchema = zod.z.object({
41
41
  allowNpx: zod.z.boolean().optional(),
42
42
  env: zod.z.record(zod.z.string(), zod.z.string()).optional(),
43
43
  verbose: zod.z.boolean().optional(),
44
- logger: zod.z.any().optional()
44
+ logger: zod.z.any().optional(),
45
+ // NEW: Reasoning & Verbosity
46
+ reasoningEffort: zod.z.enum(["minimal", "low", "medium", "high"]).optional(),
47
+ // Note: API rejects 'concise' and 'none' despite error messages claiming they're valid
48
+ reasoningSummary: zod.z.enum(["auto", "detailed"]).optional(),
49
+ reasoningSummaryFormat: zod.z.enum(["none", "experimental"]).optional(),
50
+ modelVerbosity: zod.z.enum(["low", "medium", "high"]).optional(),
51
+ // NEW: Advanced features
52
+ includePlanTool: zod.z.boolean().optional(),
53
+ profile: zod.z.string().optional(),
54
+ oss: zod.z.boolean().optional(),
55
+ webSearch: zod.z.boolean().optional(),
56
+ // NEW: Generic overrides
57
+ configOverrides: zod.z.record(
58
+ zod.z.string(),
59
+ zod.z.union([
60
+ zod.z.string(),
61
+ zod.z.number(),
62
+ zod.z.boolean(),
63
+ zod.z.object({}).passthrough(),
64
+ zod.z.array(zod.z.any())
65
+ ])
66
+ ).optional()
45
67
  }).strict();
46
68
  function validateSettings(settings) {
47
69
  const warnings = [];
@@ -169,6 +191,22 @@ function isAuthenticationError(err) {
169
191
  }
170
192
 
171
193
  // src/codex-cli-language-model.ts
194
+ var codexCliProviderOptionsSchema = zod.z.object({
195
+ reasoningEffort: zod.z.enum(["minimal", "low", "medium", "high"]).optional(),
196
+ reasoningSummary: zod.z.enum(["auto", "detailed"]).optional(),
197
+ reasoningSummaryFormat: zod.z.enum(["none", "experimental"]).optional(),
198
+ textVerbosity: zod.z.enum(["low", "medium", "high"]).optional(),
199
+ configOverrides: zod.z.record(
200
+ zod.z.string(),
201
+ zod.z.union([
202
+ zod.z.string(),
203
+ zod.z.number(),
204
+ zod.z.boolean(),
205
+ zod.z.object({}).passthrough(),
206
+ zod.z.array(zod.z.any())
207
+ ])
208
+ ).optional()
209
+ }).strict();
172
210
  function resolveCodexPath(explicitPath, allowNpx) {
173
211
  if (explicitPath) return { cmd: "node", args: [explicitPath] };
174
212
  try {
@@ -202,55 +240,150 @@ var CodexCliLanguageModel = class {
202
240
  const warn = validateModelId(this.modelId);
203
241
  if (warn) this.logger.warn(`Codex CLI model: ${warn}`);
204
242
  }
205
- buildArgs(promptText, responseFormat) {
206
- const base = resolveCodexPath(this.settings.codexPath, this.settings.allowNpx);
243
+ mergeSettings(providerOptions) {
244
+ if (!providerOptions) return this.settings;
245
+ const mergedConfigOverrides = providerOptions.configOverrides || this.settings.configOverrides ? {
246
+ ...this.settings.configOverrides ?? {},
247
+ ...providerOptions.configOverrides ?? {}
248
+ } : void 0;
249
+ return {
250
+ ...this.settings,
251
+ reasoningEffort: providerOptions.reasoningEffort ?? this.settings.reasoningEffort,
252
+ reasoningSummary: providerOptions.reasoningSummary ?? this.settings.reasoningSummary,
253
+ reasoningSummaryFormat: providerOptions.reasoningSummaryFormat ?? this.settings.reasoningSummaryFormat,
254
+ modelVerbosity: providerOptions.textVerbosity ?? this.settings.modelVerbosity,
255
+ configOverrides: mergedConfigOverrides
256
+ };
257
+ }
258
+ // Codex JSONL items use `type` for the item discriminator, but some
259
+ // earlier fixtures (and defensive parsing) might still surface `item_type`.
260
+ // This helper returns whichever is present.
261
+ getItemType(item) {
262
+ if (!item) return void 0;
263
+ const data = item;
264
+ const legacy = typeof data.item_type === "string" ? data.item_type : void 0;
265
+ const current = typeof data.type === "string" ? data.type : void 0;
266
+ return legacy ?? current;
267
+ }
268
+ buildArgs(promptText, responseFormat, settings = this.settings) {
269
+ const base = resolveCodexPath(settings.codexPath, settings.allowNpx);
207
270
  const args = [...base.args, "exec", "--experimental-json"];
208
- if (this.settings.fullAuto) {
271
+ if (settings.fullAuto) {
209
272
  args.push("--full-auto");
210
- } else if (this.settings.dangerouslyBypassApprovalsAndSandbox) {
273
+ } else if (settings.dangerouslyBypassApprovalsAndSandbox) {
211
274
  args.push("--dangerously-bypass-approvals-and-sandbox");
212
275
  } else {
213
- const approval = this.settings.approvalMode ?? "on-failure";
276
+ const approval = settings.approvalMode ?? "on-failure";
214
277
  args.push("-c", `approval_policy=${approval}`);
215
- const sandbox = this.settings.sandboxMode ?? "workspace-write";
278
+ const sandbox = settings.sandboxMode ?? "workspace-write";
216
279
  args.push("-c", `sandbox_mode=${sandbox}`);
217
280
  }
218
- if (this.settings.skipGitRepoCheck !== false) {
281
+ if (settings.skipGitRepoCheck !== false) {
219
282
  args.push("--skip-git-repo-check");
220
283
  }
221
- if (this.settings.color) {
222
- args.push("--color", this.settings.color);
284
+ if (settings.reasoningEffort) {
285
+ args.push("-c", `model_reasoning_effort=${settings.reasoningEffort}`);
286
+ }
287
+ if (settings.reasoningSummary) {
288
+ args.push("-c", `model_reasoning_summary=${settings.reasoningSummary}`);
289
+ }
290
+ if (settings.reasoningSummaryFormat) {
291
+ args.push("-c", `model_reasoning_summary_format=${settings.reasoningSummaryFormat}`);
292
+ }
293
+ if (settings.modelVerbosity) {
294
+ args.push("-c", `model_verbosity=${settings.modelVerbosity}`);
295
+ }
296
+ if (settings.includePlanTool) {
297
+ args.push("--include-plan-tool");
298
+ }
299
+ if (settings.profile) {
300
+ args.push("--profile", settings.profile);
301
+ }
302
+ if (settings.oss) {
303
+ args.push("--oss");
304
+ }
305
+ if (settings.webSearch) {
306
+ args.push("-c", "tools.web_search=true");
307
+ }
308
+ if (settings.color) {
309
+ args.push("--color", settings.color);
223
310
  }
224
311
  if (this.modelId) {
225
312
  args.push("-m", this.modelId);
226
313
  }
314
+ if (settings.configOverrides) {
315
+ for (const [key, value] of Object.entries(settings.configOverrides)) {
316
+ this.addConfigOverride(args, key, value);
317
+ }
318
+ }
227
319
  let schemaPath;
228
320
  if (responseFormat?.type === "json" && responseFormat.schema) {
229
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "codex-schema-"));
230
- schemaPath = path.join(dir, "schema.json");
231
321
  const schema = typeof responseFormat.schema === "object" ? responseFormat.schema : {};
232
322
  const sanitizedSchema = this.sanitizeJsonSchema(schema);
233
- const schemaWithAdditional = {
234
- ...sanitizedSchema,
235
- // OpenAI strict mode requires additionalProperties=false even if user requested otherwise.
236
- additionalProperties: false
237
- };
238
- fs.writeFileSync(schemaPath, JSON.stringify(schemaWithAdditional, null, 2));
239
- args.push("--output-schema", schemaPath);
323
+ const hasProperties = Object.keys(sanitizedSchema).length > 0;
324
+ if (hasProperties) {
325
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "codex-schema-"));
326
+ schemaPath = path.join(dir, "schema.json");
327
+ const schemaWithAdditional = {
328
+ ...sanitizedSchema,
329
+ additionalProperties: false
330
+ };
331
+ fs.writeFileSync(schemaPath, JSON.stringify(schemaWithAdditional, null, 2));
332
+ args.push("--output-schema", schemaPath);
333
+ }
240
334
  }
241
335
  args.push(promptText);
242
336
  const env = {
243
337
  ...process.env,
244
- ...this.settings.env || {},
338
+ ...settings.env || {},
245
339
  RUST_LOG: process.env.RUST_LOG || "error"
246
340
  };
247
- let lastMessagePath = this.settings.outputLastMessageFile;
341
+ let lastMessagePath = settings.outputLastMessageFile;
248
342
  if (!lastMessagePath) {
249
343
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "codex-cli-"));
250
344
  lastMessagePath = path.join(dir, "last-message.txt");
251
345
  }
252
346
  args.push("--output-last-message", lastMessagePath);
253
- return { cmd: base.cmd, args, env, cwd: this.settings.cwd, lastMessagePath, schemaPath };
347
+ return { cmd: base.cmd, args, env, cwd: settings.cwd, lastMessagePath, schemaPath };
348
+ }
349
+ addConfigOverride(args, key, value) {
350
+ if (this.isPlainObject(value)) {
351
+ for (const [childKey, childValue] of Object.entries(value)) {
352
+ this.addConfigOverride(
353
+ args,
354
+ `${key}.${childKey}`,
355
+ childValue
356
+ );
357
+ }
358
+ return;
359
+ }
360
+ const serialized = this.serializeConfigValue(value);
361
+ args.push("-c", `${key}=${serialized}`);
362
+ }
363
+ /**
364
+ * Serialize a config override value into a CLI-safe string.
365
+ */
366
+ serializeConfigValue(value) {
367
+ if (typeof value === "string") return value;
368
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
369
+ if (Array.isArray(value)) {
370
+ try {
371
+ return JSON.stringify(value);
372
+ } catch {
373
+ return String(value);
374
+ }
375
+ }
376
+ if (value && typeof value === "object") {
377
+ try {
378
+ return JSON.stringify(value);
379
+ } catch {
380
+ return String(value);
381
+ }
382
+ }
383
+ return String(value);
384
+ }
385
+ isPlainObject(value) {
386
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.prototype.toString.call(value) === "[object Object]";
254
387
  }
255
388
  sanitizeJsonSchema(value) {
256
389
  if (typeof value !== "object" || value === null) {
@@ -300,34 +433,183 @@ var CodexCliLanguageModel = class {
300
433
  }
301
434
  parseExperimentalJsonEvent(line) {
302
435
  try {
303
- const evt = JSON.parse(line);
304
- const result = {};
305
- switch (evt.type) {
306
- case "session.created":
307
- result.sessionId = evt.session_id;
308
- break;
309
- case "turn.completed":
310
- if (evt.usage) {
311
- result.usage = {
312
- inputTokens: evt.usage.input_tokens || 0,
313
- outputTokens: evt.usage.output_tokens || 0
314
- };
315
- }
316
- break;
317
- case "item.completed":
318
- if (evt.item?.item_type === "assistant_message" || evt.item?.item_type === "reasoning") {
319
- result.text = evt.item.text;
320
- }
321
- break;
322
- case "error":
323
- result.error = evt.message;
324
- break;
436
+ return JSON.parse(line);
437
+ } catch {
438
+ return void 0;
439
+ }
440
+ }
441
+ extractUsage(evt) {
442
+ const reported = evt.usage;
443
+ if (!reported) return void 0;
444
+ const inputTokens = reported.input_tokens ?? 0;
445
+ const outputTokens = reported.output_tokens ?? 0;
446
+ const cachedInputTokens = reported.cached_input_tokens ?? 0;
447
+ return {
448
+ inputTokens,
449
+ outputTokens,
450
+ // totalTokens should not double-count cached tokens; track cached separately
451
+ totalTokens: inputTokens + outputTokens,
452
+ cachedInputTokens
453
+ };
454
+ }
455
+ getToolName(item) {
456
+ if (!item) return void 0;
457
+ const itemType = this.getItemType(item);
458
+ switch (itemType) {
459
+ case "command_execution":
460
+ return "exec";
461
+ case "file_change":
462
+ return "patch";
463
+ case "mcp_tool_call": {
464
+ const tool = item.tool;
465
+ if (typeof tool === "string" && tool.length > 0) return tool;
466
+ return "mcp_tool";
467
+ }
468
+ case "web_search":
469
+ return "web_search";
470
+ default:
471
+ return void 0;
472
+ }
473
+ }
474
+ buildToolInputPayload(item) {
475
+ if (!item) return void 0;
476
+ const data = item;
477
+ switch (this.getItemType(item)) {
478
+ case "command_execution": {
479
+ const payload = {};
480
+ if (typeof data.command === "string") payload.command = data.command;
481
+ if (typeof data.status === "string") payload.status = data.status;
482
+ if (typeof data.cwd === "string") payload.cwd = data.cwd;
483
+ return Object.keys(payload).length ? payload : void 0;
484
+ }
485
+ case "file_change": {
486
+ const payload = {};
487
+ if (Array.isArray(data.changes)) payload.changes = data.changes;
488
+ if (typeof data.status === "string") payload.status = data.status;
489
+ return Object.keys(payload).length ? payload : void 0;
490
+ }
491
+ case "mcp_tool_call": {
492
+ const payload = {};
493
+ if (typeof data.server === "string") payload.server = data.server;
494
+ if (typeof data.tool === "string") payload.tool = data.tool;
495
+ if (typeof data.status === "string") payload.status = data.status;
496
+ if (data.arguments !== void 0) payload.arguments = data.arguments;
497
+ return Object.keys(payload).length ? payload : void 0;
498
+ }
499
+ case "web_search": {
500
+ const payload = {};
501
+ if (typeof data.query === "string") payload.query = data.query;
502
+ return Object.keys(payload).length ? payload : void 0;
503
+ }
504
+ default:
505
+ return void 0;
506
+ }
507
+ }
508
+ buildToolResultPayload(item) {
509
+ if (!item) return { result: {} };
510
+ const data = item;
511
+ const metadata = {};
512
+ const itemType = this.getItemType(item);
513
+ if (typeof itemType === "string") metadata.itemType = itemType;
514
+ if (typeof item.id === "string") metadata.itemId = item.id;
515
+ if (typeof data.status === "string") metadata.status = data.status;
516
+ const buildResult = (result) => ({
517
+ result,
518
+ metadata: Object.keys(metadata).length ? metadata : void 0
519
+ });
520
+ switch (itemType) {
521
+ case "command_execution": {
522
+ const result = {};
523
+ if (typeof data.command === "string") result.command = data.command;
524
+ if (typeof data.aggregated_output === "string")
525
+ result.aggregatedOutput = data.aggregated_output;
526
+ if (typeof data.exit_code === "number") result.exitCode = data.exit_code;
527
+ if (typeof data.status === "string") result.status = data.status;
528
+ return buildResult(result);
529
+ }
530
+ case "file_change": {
531
+ const result = {};
532
+ if (Array.isArray(data.changes)) result.changes = data.changes;
533
+ if (typeof data.status === "string") result.status = data.status;
534
+ return buildResult(result);
535
+ }
536
+ case "mcp_tool_call": {
537
+ const result = {};
538
+ if (typeof data.server === "string") {
539
+ result.server = data.server;
540
+ metadata.server = data.server;
541
+ }
542
+ if (typeof data.tool === "string") result.tool = data.tool;
543
+ if (typeof data.status === "string") result.status = data.status;
544
+ if (data.result !== void 0) result.result = data.result;
545
+ if (data.error !== void 0) result.error = data.error;
546
+ return buildResult(result);
325
547
  }
326
- return result;
548
+ case "web_search": {
549
+ const result = {};
550
+ if (typeof data.query === "string") result.query = data.query;
551
+ if (typeof data.status === "string") result.status = data.status;
552
+ return buildResult(result);
553
+ }
554
+ default: {
555
+ const result = { ...data };
556
+ return buildResult(result);
557
+ }
558
+ }
559
+ }
560
+ safeStringify(value) {
561
+ if (value === void 0) return "";
562
+ if (typeof value === "string") return value;
563
+ try {
564
+ return JSON.stringify(value);
327
565
  } catch {
328
- return {};
566
+ return "";
329
567
  }
330
568
  }
569
+ emitToolInvocation(controller, toolCallId, toolName, inputPayload) {
570
+ const inputString = this.safeStringify(inputPayload);
571
+ controller.enqueue({ type: "tool-input-start", id: toolCallId, toolName });
572
+ if (inputString) {
573
+ controller.enqueue({ type: "tool-input-delta", id: toolCallId, delta: inputString });
574
+ }
575
+ controller.enqueue({ type: "tool-input-end", id: toolCallId });
576
+ controller.enqueue({
577
+ type: "tool-call",
578
+ toolCallId,
579
+ toolName,
580
+ input: inputString,
581
+ providerExecuted: true
582
+ });
583
+ }
584
+ emitToolResult(controller, toolCallId, toolName, item, resultPayload, metadata) {
585
+ const providerMetadataEntries = {
586
+ ...metadata ?? {}
587
+ };
588
+ const itemType = this.getItemType(item);
589
+ if (itemType && providerMetadataEntries.itemType === void 0) {
590
+ providerMetadataEntries.itemType = itemType;
591
+ }
592
+ if (item.id && providerMetadataEntries.itemId === void 0) {
593
+ providerMetadataEntries.itemId = item.id;
594
+ }
595
+ let isError;
596
+ if (itemType === "command_execution") {
597
+ const data = item;
598
+ const exitCode = typeof data.exit_code === "number" ? data.exit_code : void 0;
599
+ const status = typeof data.status === "string" ? data.status : void 0;
600
+ if (exitCode !== void 0 && exitCode !== 0 || status === "failed") {
601
+ isError = true;
602
+ }
603
+ }
604
+ controller.enqueue({
605
+ type: "tool-result",
606
+ toolCallId,
607
+ toolName,
608
+ result: resultPayload ?? {},
609
+ ...isError ? { isError: true } : {},
610
+ ...Object.keys(providerMetadataEntries).length ? { providerMetadata: { "codex-cli": providerMetadataEntries } } : {}
611
+ });
612
+ }
331
613
  handleSpawnError(err, promptExcerpt) {
332
614
  const e = err && typeof err === "object" ? err : void 0;
333
615
  const message = String((e?.message ?? err) || "Failed to run Codex CLI");
@@ -349,10 +631,17 @@ var CodexCliLanguageModel = class {
349
631
  ...this.mapWarnings(options),
350
632
  ...mappingWarnings?.map((m) => ({ type: "other", message: m })) || []
351
633
  ];
634
+ const providerOptions = await providerUtils.parseProviderOptions({
635
+ provider: this.provider,
636
+ providerOptions: options.providerOptions,
637
+ schema: codexCliProviderOptionsSchema
638
+ });
639
+ const effectiveSettings = this.mergeSettings(providerOptions);
352
640
  const responseFormat = options.responseFormat?.type === "json" ? { type: "json", schema: options.responseFormat.schema } : void 0;
353
641
  const { cmd, args, env, cwd, lastMessagePath, schemaPath } = this.buildArgs(
354
642
  promptText,
355
- responseFormat
643
+ responseFormat,
644
+ effectiveSettings
356
645
  );
357
646
  let text = "";
358
647
  const usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
@@ -370,25 +659,56 @@ var CodexCliLanguageModel = class {
370
659
  try {
371
660
  await new Promise((resolve, reject) => {
372
661
  let stderr = "";
662
+ let turnFailureMessage;
373
663
  child.stderr.on("data", (d) => stderr += String(d));
374
664
  child.stdout.setEncoding("utf8");
375
665
  child.stdout.on("data", (chunk) => {
376
666
  const lines = chunk.split(/\r?\n/).filter(Boolean);
377
667
  for (const line of lines) {
378
- const parsed = this.parseExperimentalJsonEvent(line);
379
- if (parsed.sessionId) this.sessionId = parsed.sessionId;
380
- if (parsed.text) text = parsed.text;
381
- if (parsed.usage) {
382
- usage.inputTokens = parsed.usage.inputTokens;
383
- usage.outputTokens = parsed.usage.outputTokens;
384
- usage.totalTokens = usage.inputTokens + usage.outputTokens;
668
+ const event = this.parseExperimentalJsonEvent(line);
669
+ if (!event) continue;
670
+ if (event.type === "thread.started" && typeof event.thread_id === "string") {
671
+ this.sessionId = event.thread_id;
672
+ }
673
+ if (event.type === "session.created" && typeof event.session_id === "string") {
674
+ this.sessionId = event.session_id;
675
+ }
676
+ if (event.type === "turn.completed") {
677
+ const usageEvent = this.extractUsage(event);
678
+ if (usageEvent) {
679
+ usage.inputTokens = usageEvent.inputTokens;
680
+ usage.outputTokens = usageEvent.outputTokens;
681
+ usage.totalTokens = usageEvent.totalTokens;
682
+ }
683
+ }
684
+ if (event.type === "item.completed" && this.getItemType(event.item) === "assistant_message" && typeof event.item?.text === "string") {
685
+ text = event.item.text;
686
+ }
687
+ if (event.type === "turn.failed") {
688
+ const errorText = event.error && typeof event.error.message === "string" && event.error.message || (typeof event.message === "string" ? event.message : void 0);
689
+ turnFailureMessage = errorText ?? turnFailureMessage ?? "Codex turn failed";
690
+ }
691
+ if (event.type === "error") {
692
+ const errorText = typeof event.message === "string" ? event.message : void 0;
693
+ turnFailureMessage = errorText ?? turnFailureMessage ?? "Codex error";
385
694
  }
386
695
  }
387
696
  });
388
697
  child.on("error", (e) => reject(this.handleSpawnError(e, promptExcerpt)));
389
698
  child.on("close", (code) => {
390
- if (code === 0) resolve();
391
- else
699
+ if (code === 0) {
700
+ if (turnFailureMessage) {
701
+ reject(
702
+ createAPICallError({
703
+ message: turnFailureMessage,
704
+ stderr,
705
+ promptExcerpt
706
+ })
707
+ );
708
+ return;
709
+ }
710
+ resolve();
711
+ } else {
392
712
  reject(
393
713
  createAPICallError({
394
714
  message: `Codex CLI exited with code ${code}`,
@@ -397,6 +717,7 @@ var CodexCliLanguageModel = class {
397
717
  promptExcerpt
398
718
  })
399
719
  );
720
+ }
400
721
  });
401
722
  });
402
723
  } finally {
@@ -442,10 +763,17 @@ var CodexCliLanguageModel = class {
442
763
  ...this.mapWarnings(options),
443
764
  ...mappingWarnings?.map((m) => ({ type: "other", message: m })) || []
444
765
  ];
766
+ const providerOptions = await providerUtils.parseProviderOptions({
767
+ provider: this.provider,
768
+ providerOptions: options.providerOptions,
769
+ schema: codexCliProviderOptionsSchema
770
+ });
771
+ const effectiveSettings = this.mergeSettings(providerOptions);
445
772
  const responseFormat = options.responseFormat?.type === "json" ? { type: "json", schema: options.responseFormat.schema } : void 0;
446
773
  const { cmd, args, env, cwd, lastMessagePath, schemaPath } = this.buildArgs(
447
774
  promptText,
448
- responseFormat
775
+ responseFormat,
776
+ effectiveSettings
449
777
  );
450
778
  const stream = new ReadableStream({
451
779
  start: (controller) => {
@@ -453,6 +781,69 @@ var CodexCliLanguageModel = class {
453
781
  controller.enqueue({ type: "stream-start", warnings });
454
782
  let stderr = "";
455
783
  let accumulatedText = "";
784
+ const activeTools = /* @__PURE__ */ new Map();
785
+ let responseMetadataSent = false;
786
+ let lastUsage;
787
+ let turnFailureMessage;
788
+ const sendMetadata = (meta = {}) => {
789
+ controller.enqueue({
790
+ type: "response-metadata",
791
+ id: crypto.randomUUID(),
792
+ timestamp: /* @__PURE__ */ new Date(),
793
+ modelId: this.modelId,
794
+ ...Object.keys(meta).length ? { providerMetadata: { "codex-cli": meta } } : {}
795
+ });
796
+ };
797
+ const handleItemEvent = (event) => {
798
+ const item = event.item;
799
+ if (!item) return;
800
+ if (event.type === "item.completed" && this.getItemType(item) === "assistant_message" && typeof item.text === "string") {
801
+ accumulatedText = item.text;
802
+ return;
803
+ }
804
+ const toolName = this.getToolName(item);
805
+ if (!toolName) {
806
+ return;
807
+ }
808
+ const mapKey = typeof item.id === "string" && item.id.length > 0 ? item.id : crypto.randomUUID();
809
+ let toolState = activeTools.get(mapKey);
810
+ const latestInput = this.buildToolInputPayload(item);
811
+ if (!toolState) {
812
+ toolState = {
813
+ toolCallId: mapKey,
814
+ toolName,
815
+ inputPayload: latestInput,
816
+ hasEmittedCall: false
817
+ };
818
+ activeTools.set(mapKey, toolState);
819
+ } else {
820
+ toolState.toolName = toolName;
821
+ if (latestInput !== void 0) {
822
+ toolState.inputPayload = latestInput;
823
+ }
824
+ }
825
+ if (!toolState.hasEmittedCall) {
826
+ this.emitToolInvocation(
827
+ controller,
828
+ toolState.toolCallId,
829
+ toolState.toolName,
830
+ toolState.inputPayload
831
+ );
832
+ toolState.hasEmittedCall = true;
833
+ }
834
+ if (event.type === "item.completed") {
835
+ const { result, metadata } = this.buildToolResultPayload(item);
836
+ this.emitToolResult(
837
+ controller,
838
+ toolState.toolCallId,
839
+ toolState.toolName,
840
+ item,
841
+ result,
842
+ metadata
843
+ );
844
+ activeTools.delete(mapKey);
845
+ }
846
+ };
456
847
  const onAbort = () => {
457
848
  child.kill("SIGTERM");
458
849
  };
@@ -464,42 +855,7 @@ var CodexCliLanguageModel = class {
464
855
  }
465
856
  options.abortSignal.addEventListener("abort", onAbort, { once: true });
466
857
  }
467
- child.stderr.on("data", (d) => stderr += String(d));
468
- child.stdout.setEncoding("utf8");
469
- child.stdout.on("data", (chunk) => {
470
- const lines = chunk.split(/\r?\n/).filter(Boolean);
471
- for (const line of lines) {
472
- const parsed = this.parseExperimentalJsonEvent(line);
473
- if (parsed.sessionId) {
474
- this.sessionId = parsed.sessionId;
475
- controller.enqueue({
476
- type: "response-metadata",
477
- id: crypto.randomUUID(),
478
- timestamp: /* @__PURE__ */ new Date(),
479
- modelId: this.modelId
480
- });
481
- }
482
- if (parsed.text) {
483
- accumulatedText = parsed.text;
484
- }
485
- }
486
- });
487
- const cleanupSchema = () => {
488
- if (!schemaPath) return;
489
- try {
490
- const schemaDir = path.dirname(schemaPath);
491
- fs.rmSync(schemaDir, { recursive: true, force: true });
492
- } catch {
493
- }
494
- };
495
- child.on("error", (e) => {
496
- if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
497
- cleanupSchema();
498
- controller.error(this.handleSpawnError(e, promptExcerpt));
499
- });
500
- child.on("close", (code) => {
501
- if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
502
- cleanupSchema();
858
+ const finishStream = (code) => {
503
859
  if (code !== 0) {
504
860
  controller.error(
505
861
  createAPICallError({
@@ -511,6 +867,16 @@ var CodexCliLanguageModel = class {
511
867
  );
512
868
  return;
513
869
  }
870
+ if (turnFailureMessage) {
871
+ controller.error(
872
+ createAPICallError({
873
+ message: turnFailureMessage,
874
+ stderr,
875
+ promptExcerpt
876
+ })
877
+ );
878
+ return;
879
+ }
514
880
  let finalText = accumulatedText;
515
881
  if (!finalText && lastMessagePath) {
516
882
  try {
@@ -524,14 +890,84 @@ var CodexCliLanguageModel = class {
524
890
  }
525
891
  }
526
892
  if (finalText) {
527
- controller.enqueue({ type: "text-delta", id: crypto.randomUUID(), delta: finalText });
893
+ const textId = crypto.randomUUID();
894
+ controller.enqueue({ type: "text-start", id: textId });
895
+ controller.enqueue({ type: "text-delta", id: textId, delta: finalText });
896
+ controller.enqueue({ type: "text-end", id: textId });
528
897
  }
898
+ const usageSummary = lastUsage ?? { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
529
899
  controller.enqueue({
530
900
  type: "finish",
531
901
  finishReason: "stop",
532
- usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
902
+ usage: usageSummary
533
903
  });
534
904
  controller.close();
905
+ };
906
+ child.stderr.on("data", (d) => stderr += String(d));
907
+ child.stdout.setEncoding("utf8");
908
+ child.stdout.on("data", (chunk) => {
909
+ const lines = chunk.split(/\r?\n/).filter(Boolean);
910
+ for (const line of lines) {
911
+ const event = this.parseExperimentalJsonEvent(line);
912
+ if (!event) continue;
913
+ if (event.type === "thread.started" && typeof event.thread_id === "string") {
914
+ this.sessionId = event.thread_id;
915
+ if (!responseMetadataSent) {
916
+ responseMetadataSent = true;
917
+ sendMetadata();
918
+ }
919
+ continue;
920
+ }
921
+ if (event.type === "session.created" && typeof event.session_id === "string") {
922
+ this.sessionId = event.session_id;
923
+ if (!responseMetadataSent) {
924
+ responseMetadataSent = true;
925
+ sendMetadata();
926
+ }
927
+ continue;
928
+ }
929
+ if (event.type === "turn.completed") {
930
+ const usageEvent = this.extractUsage(event);
931
+ if (usageEvent) {
932
+ lastUsage = usageEvent;
933
+ }
934
+ continue;
935
+ }
936
+ if (event.type === "turn.failed") {
937
+ const errorText = event.error && typeof event.error.message === "string" && event.error.message || (typeof event.message === "string" ? event.message : void 0);
938
+ turnFailureMessage = errorText ?? turnFailureMessage ?? "Codex turn failed";
939
+ sendMetadata({ error: turnFailureMessage });
940
+ continue;
941
+ }
942
+ if (event.type === "error") {
943
+ const errorText = typeof event.message === "string" ? event.message : void 0;
944
+ const effective = errorText ?? "Codex error";
945
+ turnFailureMessage = turnFailureMessage ?? effective;
946
+ sendMetadata({ error: effective });
947
+ continue;
948
+ }
949
+ if (event.type && event.type.startsWith("item.")) {
950
+ handleItemEvent(event);
951
+ }
952
+ }
953
+ });
954
+ const cleanupSchema = () => {
955
+ if (!schemaPath) return;
956
+ try {
957
+ const schemaDir = path.dirname(schemaPath);
958
+ fs.rmSync(schemaDir, { recursive: true, force: true });
959
+ } catch {
960
+ }
961
+ };
962
+ child.on("error", (e) => {
963
+ if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
964
+ cleanupSchema();
965
+ controller.error(this.handleSpawnError(e, promptExcerpt));
966
+ });
967
+ child.on("close", (code) => {
968
+ if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
969
+ cleanupSchema();
970
+ setImmediate(() => finishStream(code));
535
971
  });
536
972
  },
537
973
  cancel: () => {