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