ai-sdk-provider-codex-cli 0.3.0 → 0.5.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/README.md CHANGED
@@ -15,6 +15,7 @@ A community provider for Vercel AI SDK v5 that uses OpenAI’s Codex CLI (non‑
15
15
  - Works with `generateText`, `streamText`, and `generateObject` (native JSON Schema support via `--output-schema`)
16
16
  - Uses ChatGPT OAuth from `codex login` (tokens in `~/.codex/auth.json`) or `OPENAI_API_KEY`
17
17
  - Node-only (spawns a local process); supports CI and local dev
18
+ - **v0.5.0**: Adds comprehensive logging system with verbose mode and custom logger support
18
19
  - **v0.3.0**: Adds comprehensive tool streaming support for monitoring autonomous tool execution
19
20
  - **v0.2.0 Breaking Changes**: Switched to `--experimental-json` and native schema enforcement (see [CHANGELOG](CHANGELOG.md))
20
21
 
@@ -96,6 +97,7 @@ console.log(object);
96
97
 
97
98
  - AI SDK v5 compatible (LanguageModelV2)
98
99
  - Streaming and non‑streaming
100
+ - **Configurable logging** (v0.5.0+) - Verbose mode, custom loggers, or silent operation
99
101
  - **Tool streaming support** (v0.3.0+) - Monitor autonomous tool execution in real-time
100
102
  - **Native JSON Schema support** via `--output-schema` (API-enforced with `strict: true`)
101
103
  - JSON object generation with Zod schemas (100-200 fewer tokens per request vs prompt engineering)
@@ -135,6 +137,58 @@ for await (const part of result.fullStream) {
135
137
 
136
138
  **Limitation:** Real-time output streaming (`output-delta` events) not yet available. Tool outputs delivered in final `tool-result` event. See `examples/streaming-tool-calls.mjs` and `examples/streaming-multiple-tools.mjs` for usage patterns.
137
139
 
140
+ ### Logging Configuration (v0.5.0+)
141
+
142
+ Control logging verbosity and integrate with your observability stack:
143
+
144
+ ```js
145
+ import { codexCli } from 'ai-sdk-provider-codex-cli';
146
+
147
+ // Default: warn/error only (clean production output)
148
+ const model = codexCli('gpt-5-codex', {
149
+ allowNpx: true,
150
+ skipGitRepoCheck: true,
151
+ });
152
+
153
+ // Verbose mode: enable debug/info logs for troubleshooting
154
+ const verboseModel = codexCli('gpt-5-codex', {
155
+ allowNpx: true,
156
+ skipGitRepoCheck: true,
157
+ verbose: true, // Shows all log levels
158
+ });
159
+
160
+ // Custom logger: integrate with Winston, Pino, Datadog, etc.
161
+ const customModel = codexCli('gpt-5-codex', {
162
+ allowNpx: true,
163
+ skipGitRepoCheck: true,
164
+ verbose: true,
165
+ logger: {
166
+ debug: (msg) => myLogger.debug('Codex:', msg),
167
+ info: (msg) => myLogger.info('Codex:', msg),
168
+ warn: (msg) => myLogger.warn('Codex:', msg),
169
+ error: (msg) => myLogger.error('Codex:', msg),
170
+ },
171
+ });
172
+
173
+ // Silent: disable all logging
174
+ const silentModel = codexCli('gpt-5-codex', {
175
+ allowNpx: true,
176
+ skipGitRepoCheck: true,
177
+ logger: false, // No logs at all
178
+ });
179
+ ```
180
+
181
+ **Log Levels:**
182
+
183
+ - `debug`: Detailed execution traces (verbose mode only)
184
+ - `info`: General execution flow (verbose mode only)
185
+ - `warn`: Warnings and misconfigurations (always shown)
186
+ - `error`: Errors and failures (always shown)
187
+
188
+ **Default Logger:** Adds level tags `[DEBUG]`, `[INFO]`, `[WARN]`, `[ERROR]` to console output. Use a custom logger or `logger: false` if you need different formatting.
189
+
190
+ See `examples/logging-*.mjs` for complete examples and [docs/ai-sdk-v5/guide.md](docs/ai-sdk-v5/guide.md) for detailed configuration.
191
+
138
192
  ### Text Streaming behavior
139
193
 
140
194
  **Status:** Incremental streaming not currently supported with `--experimental-json` format (expected in future Codex CLI releases)
@@ -176,9 +230,79 @@ When OpenAI adds streaming support, this provider will be updated to handle thos
176
230
  - `skipGitRepoCheck`: enable by default for CI/non‑repo contexts
177
231
  - `color`: `always` | `never` | `auto`
178
232
  - `outputLastMessageFile`: by default the provider sets a temp path and reads it to capture final text reliably
233
+ - Logging (v0.5.0+):
234
+ - `verbose`: Enable debug/info logs (default: `false` for clean output)
235
+ - `logger`: Custom logger object or `false` to disable all logging
179
236
 
180
237
  See [docs/ai-sdk-v5/configuration.md](docs/ai-sdk-v5/configuration.md) for the full list and examples.
181
238
 
239
+ ## Model Parameters & Advanced Options (v0.4.0+)
240
+
241
+ Control reasoning effort, verbosity, and advanced Codex features at model creation time:
242
+
243
+ ```ts
244
+ import { codexCli } from 'ai-sdk-provider-codex-cli';
245
+
246
+ const model = codexCli('gpt-5-codex', {
247
+ allowNpx: true,
248
+ skipGitRepoCheck: true,
249
+
250
+ // Reasoning & verbosity
251
+ reasoningEffort: 'medium', // minimal | low | medium | high
252
+ reasoningSummary: 'auto', // auto | detailed (Note: 'concise' and 'none' are rejected by API)
253
+ reasoningSummaryFormat: 'none', // none | experimental
254
+ modelVerbosity: 'high', // low | medium | high
255
+
256
+ // Advanced features
257
+ includePlanTool: true, // adds --include-plan-tool
258
+ profile: 'production', // adds --profile production
259
+ oss: false, // adds --oss when true
260
+ webSearch: true, // maps to -c tools.web_search=true
261
+
262
+ // Generic overrides (maps to -c key=value)
263
+ configOverrides: {
264
+ experimental_resume: '/tmp/session.jsonl',
265
+ sandbox_workspace_write: { network_access: true },
266
+ },
267
+ });
268
+ ```
269
+
270
+ Nested override objects are flattened to dotted keys (e.g., the example above emits
271
+ `-c sandbox_workspace_write.network_access=true`). Arrays are serialized to JSON strings.
272
+
273
+ ### Per-call overrides via `providerOptions` (v0.4.0+)
274
+
275
+ Override these parameters for individual AI SDK calls using the `providerOptions` map. Per-call
276
+ values take precedence over constructor defaults while leaving other settings intact.
277
+
278
+ ```ts
279
+ import { generateText } from 'ai';
280
+ import { codexCli } from 'ai-sdk-provider-codex-cli';
281
+
282
+ const model = codexCli('gpt-5-codex', {
283
+ allowNpx: true,
284
+ reasoningEffort: 'medium',
285
+ modelVerbosity: 'medium',
286
+ });
287
+
288
+ const response = await generateText({
289
+ model,
290
+ prompt: 'Summarize the latest release notes.',
291
+ providerOptions: {
292
+ 'codex-cli': {
293
+ reasoningEffort: 'high',
294
+ reasoningSummary: 'detailed',
295
+ textVerbosity: 'high', // AI SDK naming; maps to model_verbosity
296
+ configOverrides: {
297
+ experimental_resume: '/tmp/resume.jsonl',
298
+ },
299
+ },
300
+ },
301
+ });
302
+ ```
303
+
304
+ **Precedence:** `providerOptions['codex-cli']` > constructor `CodexCliSettings` > Codex CLI defaults.
305
+
182
306
  ## Zod Compatibility
183
307
 
184
308
  - Peer supports `zod@^3 || ^4`
package/dist/index.cjs CHANGED
@@ -7,28 +7,67 @@ 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
15
15
 
16
16
  // src/logger.ts
17
17
  var defaultLogger = {
18
- warn: (m) => console.warn(m),
19
- error: (m) => console.error(m)
18
+ debug: (message) => console.debug(`[DEBUG] ${message}`),
19
+ info: (message) => console.info(`[INFO] ${message}`),
20
+ warn: (message) => console.warn(`[WARN] ${message}`),
21
+ error: (message) => console.error(`[ERROR] ${message}`)
20
22
  };
21
23
  var noopLogger = {
24
+ debug: () => {
25
+ },
26
+ info: () => {
27
+ },
22
28
  warn: () => {
23
29
  },
24
30
  error: () => {
25
31
  }
26
32
  };
27
33
  function getLogger(logger) {
28
- if (logger === false) return noopLogger;
29
- if (!logger) return defaultLogger;
34
+ if (logger === false) {
35
+ return noopLogger;
36
+ }
37
+ if (logger === void 0) {
38
+ return defaultLogger;
39
+ }
30
40
  return logger;
31
41
  }
42
+ function createVerboseLogger(logger, verbose = false) {
43
+ if (verbose) {
44
+ return logger;
45
+ }
46
+ return {
47
+ debug: () => {
48
+ },
49
+ // No-op when not verbose
50
+ info: () => {
51
+ },
52
+ // No-op when not verbose
53
+ warn: logger.warn.bind(logger),
54
+ error: logger.error.bind(logger)
55
+ };
56
+ }
57
+ var loggerFunctionSchema = zod.z.object({
58
+ debug: zod.z.any().refine((val) => typeof val === "function", {
59
+ message: "debug must be a function"
60
+ }),
61
+ info: zod.z.any().refine((val) => typeof val === "function", {
62
+ message: "info must be a function"
63
+ }),
64
+ warn: zod.z.any().refine((val) => typeof val === "function", {
65
+ message: "warn must be a function"
66
+ }),
67
+ error: zod.z.any().refine((val) => typeof val === "function", {
68
+ message: "error must be a function"
69
+ })
70
+ });
32
71
  var settingsSchema = zod.z.object({
33
72
  codexPath: zod.z.string().optional(),
34
73
  cwd: zod.z.string().optional(),
@@ -41,7 +80,29 @@ var settingsSchema = zod.z.object({
41
80
  allowNpx: zod.z.boolean().optional(),
42
81
  env: zod.z.record(zod.z.string(), zod.z.string()).optional(),
43
82
  verbose: zod.z.boolean().optional(),
44
- logger: zod.z.any().optional()
83
+ logger: zod.z.union([zod.z.literal(false), loggerFunctionSchema]).optional(),
84
+ // NEW: Reasoning & Verbosity
85
+ reasoningEffort: zod.z.enum(["minimal", "low", "medium", "high"]).optional(),
86
+ // Note: API rejects 'concise' and 'none' despite error messages claiming they're valid
87
+ reasoningSummary: zod.z.enum(["auto", "detailed"]).optional(),
88
+ reasoningSummaryFormat: zod.z.enum(["none", "experimental"]).optional(),
89
+ modelVerbosity: zod.z.enum(["low", "medium", "high"]).optional(),
90
+ // NEW: Advanced features
91
+ includePlanTool: zod.z.boolean().optional(),
92
+ profile: zod.z.string().optional(),
93
+ oss: zod.z.boolean().optional(),
94
+ webSearch: zod.z.boolean().optional(),
95
+ // NEW: Generic overrides
96
+ configOverrides: zod.z.record(
97
+ zod.z.string(),
98
+ zod.z.union([
99
+ zod.z.string(),
100
+ zod.z.number(),
101
+ zod.z.boolean(),
102
+ zod.z.object({}).passthrough(),
103
+ zod.z.array(zod.z.any())
104
+ ])
105
+ ).optional()
45
106
  }).strict();
46
107
  function validateSettings(settings) {
47
108
  const warnings = [];
@@ -169,6 +230,22 @@ function isAuthenticationError(err) {
169
230
  }
170
231
 
171
232
  // src/codex-cli-language-model.ts
233
+ var codexCliProviderOptionsSchema = zod.z.object({
234
+ reasoningEffort: zod.z.enum(["minimal", "low", "medium", "high"]).optional(),
235
+ reasoningSummary: zod.z.enum(["auto", "detailed"]).optional(),
236
+ reasoningSummaryFormat: zod.z.enum(["none", "experimental"]).optional(),
237
+ textVerbosity: zod.z.enum(["low", "medium", "high"]).optional(),
238
+ configOverrides: zod.z.record(
239
+ zod.z.string(),
240
+ zod.z.union([
241
+ zod.z.string(),
242
+ zod.z.number(),
243
+ zod.z.boolean(),
244
+ zod.z.object({}).passthrough(),
245
+ zod.z.array(zod.z.any())
246
+ ])
247
+ ).optional()
248
+ }).strict();
172
249
  function resolveCodexPath(explicitPath, allowNpx) {
173
250
  if (explicitPath) return { cmd: "node", args: [explicitPath] };
174
251
  try {
@@ -195,13 +272,29 @@ var CodexCliLanguageModel = class {
195
272
  constructor(options) {
196
273
  this.modelId = options.id;
197
274
  this.settings = options.settings ?? {};
198
- this.logger = getLogger(this.settings.logger);
275
+ const baseLogger = getLogger(this.settings.logger);
276
+ this.logger = createVerboseLogger(baseLogger, this.settings.verbose ?? false);
199
277
  if (!this.modelId || this.modelId.trim() === "") {
200
278
  throw new provider.NoSuchModelError({ modelId: this.modelId, modelType: "languageModel" });
201
279
  }
202
280
  const warn = validateModelId(this.modelId);
203
281
  if (warn) this.logger.warn(`Codex CLI model: ${warn}`);
204
282
  }
283
+ mergeSettings(providerOptions) {
284
+ if (!providerOptions) return this.settings;
285
+ const mergedConfigOverrides = providerOptions.configOverrides || this.settings.configOverrides ? {
286
+ ...this.settings.configOverrides ?? {},
287
+ ...providerOptions.configOverrides ?? {}
288
+ } : void 0;
289
+ return {
290
+ ...this.settings,
291
+ reasoningEffort: providerOptions.reasoningEffort ?? this.settings.reasoningEffort,
292
+ reasoningSummary: providerOptions.reasoningSummary ?? this.settings.reasoningSummary,
293
+ reasoningSummaryFormat: providerOptions.reasoningSummaryFormat ?? this.settings.reasoningSummaryFormat,
294
+ modelVerbosity: providerOptions.textVerbosity ?? this.settings.modelVerbosity,
295
+ configOverrides: mergedConfigOverrides
296
+ };
297
+ }
205
298
  // Codex JSONL items use `type` for the item discriminator, but some
206
299
  // earlier fixtures (and defensive parsing) might still surface `item_type`.
207
300
  // This helper returns whichever is present.
@@ -212,28 +305,57 @@ var CodexCliLanguageModel = class {
212
305
  const current = typeof data.type === "string" ? data.type : void 0;
213
306
  return legacy ?? current;
214
307
  }
215
- buildArgs(promptText, responseFormat) {
216
- const base = resolveCodexPath(this.settings.codexPath, this.settings.allowNpx);
308
+ buildArgs(promptText, responseFormat, settings = this.settings) {
309
+ const base = resolveCodexPath(settings.codexPath, settings.allowNpx);
217
310
  const args = [...base.args, "exec", "--experimental-json"];
218
- if (this.settings.fullAuto) {
311
+ if (settings.fullAuto) {
219
312
  args.push("--full-auto");
220
- } else if (this.settings.dangerouslyBypassApprovalsAndSandbox) {
313
+ } else if (settings.dangerouslyBypassApprovalsAndSandbox) {
221
314
  args.push("--dangerously-bypass-approvals-and-sandbox");
222
315
  } else {
223
- const approval = this.settings.approvalMode ?? "on-failure";
316
+ const approval = settings.approvalMode ?? "on-failure";
224
317
  args.push("-c", `approval_policy=${approval}`);
225
- const sandbox = this.settings.sandboxMode ?? "workspace-write";
318
+ const sandbox = settings.sandboxMode ?? "workspace-write";
226
319
  args.push("-c", `sandbox_mode=${sandbox}`);
227
320
  }
228
- if (this.settings.skipGitRepoCheck !== false) {
321
+ if (settings.skipGitRepoCheck !== false) {
229
322
  args.push("--skip-git-repo-check");
230
323
  }
231
- if (this.settings.color) {
232
- args.push("--color", this.settings.color);
324
+ if (settings.reasoningEffort) {
325
+ args.push("-c", `model_reasoning_effort=${settings.reasoningEffort}`);
326
+ }
327
+ if (settings.reasoningSummary) {
328
+ args.push("-c", `model_reasoning_summary=${settings.reasoningSummary}`);
329
+ }
330
+ if (settings.reasoningSummaryFormat) {
331
+ args.push("-c", `model_reasoning_summary_format=${settings.reasoningSummaryFormat}`);
332
+ }
333
+ if (settings.modelVerbosity) {
334
+ args.push("-c", `model_verbosity=${settings.modelVerbosity}`);
335
+ }
336
+ if (settings.includePlanTool) {
337
+ args.push("--include-plan-tool");
338
+ }
339
+ if (settings.profile) {
340
+ args.push("--profile", settings.profile);
341
+ }
342
+ if (settings.oss) {
343
+ args.push("--oss");
344
+ }
345
+ if (settings.webSearch) {
346
+ args.push("-c", "tools.web_search=true");
347
+ }
348
+ if (settings.color) {
349
+ args.push("--color", settings.color);
233
350
  }
234
351
  if (this.modelId) {
235
352
  args.push("-m", this.modelId);
236
353
  }
354
+ if (settings.configOverrides) {
355
+ for (const [key, value] of Object.entries(settings.configOverrides)) {
356
+ this.addConfigOverride(args, key, value);
357
+ }
358
+ }
237
359
  let schemaPath;
238
360
  if (responseFormat?.type === "json" && responseFormat.schema) {
239
361
  const schema = typeof responseFormat.schema === "object" ? responseFormat.schema : {};
@@ -253,16 +375,55 @@ var CodexCliLanguageModel = class {
253
375
  args.push(promptText);
254
376
  const env = {
255
377
  ...process.env,
256
- ...this.settings.env || {},
378
+ ...settings.env || {},
257
379
  RUST_LOG: process.env.RUST_LOG || "error"
258
380
  };
259
- let lastMessagePath = this.settings.outputLastMessageFile;
381
+ let lastMessagePath = settings.outputLastMessageFile;
260
382
  if (!lastMessagePath) {
261
383
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "codex-cli-"));
262
384
  lastMessagePath = path.join(dir, "last-message.txt");
263
385
  }
264
386
  args.push("--output-last-message", lastMessagePath);
265
- return { cmd: base.cmd, args, env, cwd: this.settings.cwd, lastMessagePath, schemaPath };
387
+ return { cmd: base.cmd, args, env, cwd: settings.cwd, lastMessagePath, schemaPath };
388
+ }
389
+ addConfigOverride(args, key, value) {
390
+ if (this.isPlainObject(value)) {
391
+ for (const [childKey, childValue] of Object.entries(value)) {
392
+ this.addConfigOverride(
393
+ args,
394
+ `${key}.${childKey}`,
395
+ childValue
396
+ );
397
+ }
398
+ return;
399
+ }
400
+ const serialized = this.serializeConfigValue(value);
401
+ args.push("-c", `${key}=${serialized}`);
402
+ }
403
+ /**
404
+ * Serialize a config override value into a CLI-safe string.
405
+ */
406
+ serializeConfigValue(value) {
407
+ if (typeof value === "string") return value;
408
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
409
+ if (Array.isArray(value)) {
410
+ try {
411
+ return JSON.stringify(value);
412
+ } catch {
413
+ return String(value);
414
+ }
415
+ }
416
+ if (value && typeof value === "object") {
417
+ try {
418
+ return JSON.stringify(value);
419
+ } catch {
420
+ return String(value);
421
+ }
422
+ }
423
+ return String(value);
424
+ }
425
+ isPlainObject(value) {
426
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.prototype.toString.call(value) === "[object Object]";
266
427
  }
267
428
  sanitizeJsonSchema(value) {
268
429
  if (typeof value !== "object" || value === null) {
@@ -504,20 +665,35 @@ var CodexCliLanguageModel = class {
504
665
  });
505
666
  }
506
667
  async doGenerate(options) {
668
+ this.logger.debug(`[codex-cli] Starting doGenerate request with model: ${this.modelId}`);
507
669
  const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
508
670
  const promptExcerpt = promptText.slice(0, 200);
509
671
  const warnings = [
510
672
  ...this.mapWarnings(options),
511
673
  ...mappingWarnings?.map((m) => ({ type: "other", message: m })) || []
512
674
  ];
675
+ this.logger.debug(
676
+ `[codex-cli] Converted ${options.prompt.length} messages, response format: ${options.responseFormat?.type ?? "none"}`
677
+ );
678
+ const providerOptions = await providerUtils.parseProviderOptions({
679
+ provider: this.provider,
680
+ providerOptions: options.providerOptions,
681
+ schema: codexCliProviderOptionsSchema
682
+ });
683
+ const effectiveSettings = this.mergeSettings(providerOptions);
513
684
  const responseFormat = options.responseFormat?.type === "json" ? { type: "json", schema: options.responseFormat.schema } : void 0;
514
685
  const { cmd, args, env, cwd, lastMessagePath, schemaPath } = this.buildArgs(
515
686
  promptText,
516
- responseFormat
687
+ responseFormat,
688
+ effectiveSettings
689
+ );
690
+ this.logger.debug(
691
+ `[codex-cli] Executing Codex CLI: ${cmd} with ${args.length} arguments, cwd: ${cwd ?? "default"}`
517
692
  );
518
693
  let text = "";
519
694
  const usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
520
695
  const finishReason = "stop";
696
+ const startTime = Date.now();
521
697
  const child = child_process.spawn(cmd, args, { env, cwd, stdio: ["ignore", "pipe", "pipe"] });
522
698
  let onAbort;
523
699
  if (options.abortSignal) {
@@ -539,11 +715,14 @@ var CodexCliLanguageModel = class {
539
715
  for (const line of lines) {
540
716
  const event = this.parseExperimentalJsonEvent(line);
541
717
  if (!event) continue;
718
+ this.logger.debug(`[codex-cli] Received event type: ${event.type ?? "unknown"}`);
542
719
  if (event.type === "thread.started" && typeof event.thread_id === "string") {
543
720
  this.sessionId = event.thread_id;
721
+ this.logger.debug(`[codex-cli] Session started: ${this.sessionId}`);
544
722
  }
545
723
  if (event.type === "session.created" && typeof event.session_id === "string") {
546
724
  this.sessionId = event.session_id;
725
+ this.logger.debug(`[codex-cli] Session created: ${this.sessionId}`);
547
726
  }
548
727
  if (event.type === "turn.completed") {
549
728
  const usageEvent = this.extractUsage(event);
@@ -559,15 +738,21 @@ var CodexCliLanguageModel = class {
559
738
  if (event.type === "turn.failed") {
560
739
  const errorText = event.error && typeof event.error.message === "string" && event.error.message || (typeof event.message === "string" ? event.message : void 0);
561
740
  turnFailureMessage = errorText ?? turnFailureMessage ?? "Codex turn failed";
741
+ this.logger.error(`[codex-cli] Turn failed: ${turnFailureMessage}`);
562
742
  }
563
743
  if (event.type === "error") {
564
744
  const errorText = typeof event.message === "string" ? event.message : void 0;
565
745
  turnFailureMessage = errorText ?? turnFailureMessage ?? "Codex error";
746
+ this.logger.error(`[codex-cli] Error event: ${turnFailureMessage}`);
566
747
  }
567
748
  }
568
749
  });
569
- child.on("error", (e) => reject(this.handleSpawnError(e, promptExcerpt)));
750
+ child.on("error", (e) => {
751
+ this.logger.error(`[codex-cli] Spawn error: ${String(e)}`);
752
+ reject(this.handleSpawnError(e, promptExcerpt));
753
+ });
570
754
  child.on("close", (code) => {
755
+ const duration = Date.now() - startTime;
571
756
  if (code === 0) {
572
757
  if (turnFailureMessage) {
573
758
  reject(
@@ -579,8 +764,15 @@ var CodexCliLanguageModel = class {
579
764
  );
580
765
  return;
581
766
  }
767
+ this.logger.info(
768
+ `[codex-cli] Request completed - Session: ${this.sessionId ?? "N/A"}, Duration: ${duration}ms, Tokens: ${usage.totalTokens}`
769
+ );
770
+ this.logger.debug(
771
+ `[codex-cli] Token usage - Input: ${usage.inputTokens}, Output: ${usage.outputTokens}, Total: ${usage.totalTokens}`
772
+ );
582
773
  resolve();
583
774
  } else {
775
+ this.logger.error(`[codex-cli] Process exited with code ${code} after ${duration}ms`);
584
776
  reject(
585
777
  createAPICallError({
586
778
  message: `Codex CLI exited with code ${code}`,
@@ -629,19 +821,34 @@ var CodexCliLanguageModel = class {
629
821
  };
630
822
  }
631
823
  async doStream(options) {
824
+ this.logger.debug(`[codex-cli] Starting doStream request with model: ${this.modelId}`);
632
825
  const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
633
826
  const promptExcerpt = promptText.slice(0, 200);
634
827
  const warnings = [
635
828
  ...this.mapWarnings(options),
636
829
  ...mappingWarnings?.map((m) => ({ type: "other", message: m })) || []
637
830
  ];
831
+ this.logger.debug(
832
+ `[codex-cli] Converted ${options.prompt.length} messages for streaming, response format: ${options.responseFormat?.type ?? "none"}`
833
+ );
834
+ const providerOptions = await providerUtils.parseProviderOptions({
835
+ provider: this.provider,
836
+ providerOptions: options.providerOptions,
837
+ schema: codexCliProviderOptionsSchema
838
+ });
839
+ const effectiveSettings = this.mergeSettings(providerOptions);
638
840
  const responseFormat = options.responseFormat?.type === "json" ? { type: "json", schema: options.responseFormat.schema } : void 0;
639
841
  const { cmd, args, env, cwd, lastMessagePath, schemaPath } = this.buildArgs(
640
842
  promptText,
641
- responseFormat
843
+ responseFormat,
844
+ effectiveSettings
845
+ );
846
+ this.logger.debug(
847
+ `[codex-cli] Executing Codex CLI for streaming: ${cmd} with ${args.length} arguments`
642
848
  );
643
849
  const stream = new ReadableStream({
644
850
  start: (controller) => {
851
+ const startTime = Date.now();
645
852
  const child = child_process.spawn(cmd, args, { env, cwd, stdio: ["ignore", "pipe", "pipe"] });
646
853
  controller.enqueue({ type: "stream-start", warnings });
647
854
  let stderr = "";
@@ -664,12 +871,18 @@ var CodexCliLanguageModel = class {
664
871
  if (!item) return;
665
872
  if (event.type === "item.completed" && this.getItemType(item) === "assistant_message" && typeof item.text === "string") {
666
873
  accumulatedText = item.text;
874
+ this.logger.debug(
875
+ `[codex-cli] Received assistant message, length: ${item.text.length}`
876
+ );
667
877
  return;
668
878
  }
669
879
  const toolName = this.getToolName(item);
670
880
  if (!toolName) {
671
881
  return;
672
882
  }
883
+ this.logger.debug(
884
+ `[codex-cli] Tool detected: ${toolName}, item type: ${this.getItemType(item)}`
885
+ );
673
886
  const mapKey = typeof item.id === "string" && item.id.length > 0 ? item.id : crypto.randomUUID();
674
887
  let toolState = activeTools.get(mapKey);
675
888
  const latestInput = this.buildToolInputPayload(item);
@@ -688,6 +901,7 @@ var CodexCliLanguageModel = class {
688
901
  }
689
902
  }
690
903
  if (!toolState.hasEmittedCall) {
904
+ this.logger.debug(`[codex-cli] Emitting tool invocation: ${toolState.toolName}`);
691
905
  this.emitToolInvocation(
692
906
  controller,
693
907
  toolState.toolCallId,
@@ -698,6 +912,7 @@ var CodexCliLanguageModel = class {
698
912
  }
699
913
  if (event.type === "item.completed") {
700
914
  const { result, metadata } = this.buildToolResultPayload(item);
915
+ this.logger.debug(`[codex-cli] Tool completed: ${toolState.toolName}`);
701
916
  this.emitToolResult(
702
917
  controller,
703
918
  toolState.toolCallId,
@@ -721,7 +936,11 @@ var CodexCliLanguageModel = class {
721
936
  options.abortSignal.addEventListener("abort", onAbort, { once: true });
722
937
  }
723
938
  const finishStream = (code) => {
939
+ const duration = Date.now() - startTime;
724
940
  if (code !== 0) {
941
+ this.logger.error(
942
+ `[codex-cli] Stream process exited with code ${code} after ${duration}ms`
943
+ );
725
944
  controller.error(
726
945
  createAPICallError({
727
946
  message: `Codex CLI exited with code ${code}`,
@@ -733,6 +952,7 @@ var CodexCliLanguageModel = class {
733
952
  return;
734
953
  }
735
954
  if (turnFailureMessage) {
955
+ this.logger.error(`[codex-cli] Stream failed: ${turnFailureMessage}`);
736
956
  controller.error(
737
957
  createAPICallError({
738
958
  message: turnFailureMessage,
@@ -761,6 +981,12 @@ var CodexCliLanguageModel = class {
761
981
  controller.enqueue({ type: "text-end", id: textId });
762
982
  }
763
983
  const usageSummary = lastUsage ?? { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
984
+ this.logger.info(
985
+ `[codex-cli] Stream completed - Session: ${this.sessionId ?? "N/A"}, Duration: ${duration}ms, Tokens: ${usageSummary.totalTokens}`
986
+ );
987
+ this.logger.debug(
988
+ `[codex-cli] Token usage - Input: ${usageSummary.inputTokens}, Output: ${usageSummary.outputTokens}, Total: ${usageSummary.totalTokens}`
989
+ );
764
990
  controller.enqueue({
765
991
  type: "finish",
766
992
  finishReason: "stop",
@@ -775,8 +1001,10 @@ var CodexCliLanguageModel = class {
775
1001
  for (const line of lines) {
776
1002
  const event = this.parseExperimentalJsonEvent(line);
777
1003
  if (!event) continue;
1004
+ this.logger.debug(`[codex-cli] Stream event: ${event.type ?? "unknown"}`);
778
1005
  if (event.type === "thread.started" && typeof event.thread_id === "string") {
779
1006
  this.sessionId = event.thread_id;
1007
+ this.logger.debug(`[codex-cli] Stream session started: ${this.sessionId}`);
780
1008
  if (!responseMetadataSent) {
781
1009
  responseMetadataSent = true;
782
1010
  sendMetadata();
@@ -785,6 +1013,7 @@ var CodexCliLanguageModel = class {
785
1013
  }
786
1014
  if (event.type === "session.created" && typeof event.session_id === "string") {
787
1015
  this.sessionId = event.session_id;
1016
+ this.logger.debug(`[codex-cli] Stream session created: ${this.sessionId}`);
788
1017
  if (!responseMetadataSent) {
789
1018
  responseMetadataSent = true;
790
1019
  sendMetadata();
@@ -801,6 +1030,7 @@ var CodexCliLanguageModel = class {
801
1030
  if (event.type === "turn.failed") {
802
1031
  const errorText = event.error && typeof event.error.message === "string" && event.error.message || (typeof event.message === "string" ? event.message : void 0);
803
1032
  turnFailureMessage = errorText ?? turnFailureMessage ?? "Codex turn failed";
1033
+ this.logger.error(`[codex-cli] Stream turn failed: ${turnFailureMessage}`);
804
1034
  sendMetadata({ error: turnFailureMessage });
805
1035
  continue;
806
1036
  }
@@ -808,6 +1038,7 @@ var CodexCliLanguageModel = class {
808
1038
  const errorText = typeof event.message === "string" ? event.message : void 0;
809
1039
  const effective = errorText ?? "Codex error";
810
1040
  turnFailureMessage = turnFailureMessage ?? effective;
1041
+ this.logger.error(`[codex-cli] Stream error event: ${effective}`);
811
1042
  sendMetadata({ error: effective });
812
1043
  continue;
813
1044
  }
@@ -825,6 +1056,7 @@ var CodexCliLanguageModel = class {
825
1056
  }
826
1057
  };
827
1058
  child.on("error", (e) => {
1059
+ this.logger.error(`[codex-cli] Stream spawn error: ${String(e)}`);
828
1060
  if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
829
1061
  cleanupSchema();
830
1062
  controller.error(this.handleSpawnError(e, promptExcerpt));