ai-sdk-provider-codex-cli 0.4.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,6 +230,9 @@ 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
 
package/dist/index.cjs CHANGED
@@ -15,20 +15,59 @@ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentS
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,7 @@ 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(),
45
84
  // NEW: Reasoning & Verbosity
46
85
  reasoningEffort: zod.z.enum(["minimal", "low", "medium", "high"]).optional(),
47
86
  // Note: API rejects 'concise' and 'none' despite error messages claiming they're valid
@@ -233,7 +272,8 @@ var CodexCliLanguageModel = class {
233
272
  constructor(options) {
234
273
  this.modelId = options.id;
235
274
  this.settings = options.settings ?? {};
236
- this.logger = getLogger(this.settings.logger);
275
+ const baseLogger = getLogger(this.settings.logger);
276
+ this.logger = createVerboseLogger(baseLogger, this.settings.verbose ?? false);
237
277
  if (!this.modelId || this.modelId.trim() === "") {
238
278
  throw new provider.NoSuchModelError({ modelId: this.modelId, modelType: "languageModel" });
239
279
  }
@@ -625,12 +665,16 @@ var CodexCliLanguageModel = class {
625
665
  });
626
666
  }
627
667
  async doGenerate(options) {
668
+ this.logger.debug(`[codex-cli] Starting doGenerate request with model: ${this.modelId}`);
628
669
  const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
629
670
  const promptExcerpt = promptText.slice(0, 200);
630
671
  const warnings = [
631
672
  ...this.mapWarnings(options),
632
673
  ...mappingWarnings?.map((m) => ({ type: "other", message: m })) || []
633
674
  ];
675
+ this.logger.debug(
676
+ `[codex-cli] Converted ${options.prompt.length} messages, response format: ${options.responseFormat?.type ?? "none"}`
677
+ );
634
678
  const providerOptions = await providerUtils.parseProviderOptions({
635
679
  provider: this.provider,
636
680
  providerOptions: options.providerOptions,
@@ -643,9 +687,13 @@ var CodexCliLanguageModel = class {
643
687
  responseFormat,
644
688
  effectiveSettings
645
689
  );
690
+ this.logger.debug(
691
+ `[codex-cli] Executing Codex CLI: ${cmd} with ${args.length} arguments, cwd: ${cwd ?? "default"}`
692
+ );
646
693
  let text = "";
647
694
  const usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
648
695
  const finishReason = "stop";
696
+ const startTime = Date.now();
649
697
  const child = child_process.spawn(cmd, args, { env, cwd, stdio: ["ignore", "pipe", "pipe"] });
650
698
  let onAbort;
651
699
  if (options.abortSignal) {
@@ -667,11 +715,14 @@ var CodexCliLanguageModel = class {
667
715
  for (const line of lines) {
668
716
  const event = this.parseExperimentalJsonEvent(line);
669
717
  if (!event) continue;
718
+ this.logger.debug(`[codex-cli] Received event type: ${event.type ?? "unknown"}`);
670
719
  if (event.type === "thread.started" && typeof event.thread_id === "string") {
671
720
  this.sessionId = event.thread_id;
721
+ this.logger.debug(`[codex-cli] Session started: ${this.sessionId}`);
672
722
  }
673
723
  if (event.type === "session.created" && typeof event.session_id === "string") {
674
724
  this.sessionId = event.session_id;
725
+ this.logger.debug(`[codex-cli] Session created: ${this.sessionId}`);
675
726
  }
676
727
  if (event.type === "turn.completed") {
677
728
  const usageEvent = this.extractUsage(event);
@@ -687,15 +738,21 @@ var CodexCliLanguageModel = class {
687
738
  if (event.type === "turn.failed") {
688
739
  const errorText = event.error && typeof event.error.message === "string" && event.error.message || (typeof event.message === "string" ? event.message : void 0);
689
740
  turnFailureMessage = errorText ?? turnFailureMessage ?? "Codex turn failed";
741
+ this.logger.error(`[codex-cli] Turn failed: ${turnFailureMessage}`);
690
742
  }
691
743
  if (event.type === "error") {
692
744
  const errorText = typeof event.message === "string" ? event.message : void 0;
693
745
  turnFailureMessage = errorText ?? turnFailureMessage ?? "Codex error";
746
+ this.logger.error(`[codex-cli] Error event: ${turnFailureMessage}`);
694
747
  }
695
748
  }
696
749
  });
697
- 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
+ });
698
754
  child.on("close", (code) => {
755
+ const duration = Date.now() - startTime;
699
756
  if (code === 0) {
700
757
  if (turnFailureMessage) {
701
758
  reject(
@@ -707,8 +764,15 @@ var CodexCliLanguageModel = class {
707
764
  );
708
765
  return;
709
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
+ );
710
773
  resolve();
711
774
  } else {
775
+ this.logger.error(`[codex-cli] Process exited with code ${code} after ${duration}ms`);
712
776
  reject(
713
777
  createAPICallError({
714
778
  message: `Codex CLI exited with code ${code}`,
@@ -757,12 +821,16 @@ var CodexCliLanguageModel = class {
757
821
  };
758
822
  }
759
823
  async doStream(options) {
824
+ this.logger.debug(`[codex-cli] Starting doStream request with model: ${this.modelId}`);
760
825
  const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
761
826
  const promptExcerpt = promptText.slice(0, 200);
762
827
  const warnings = [
763
828
  ...this.mapWarnings(options),
764
829
  ...mappingWarnings?.map((m) => ({ type: "other", message: m })) || []
765
830
  ];
831
+ this.logger.debug(
832
+ `[codex-cli] Converted ${options.prompt.length} messages for streaming, response format: ${options.responseFormat?.type ?? "none"}`
833
+ );
766
834
  const providerOptions = await providerUtils.parseProviderOptions({
767
835
  provider: this.provider,
768
836
  providerOptions: options.providerOptions,
@@ -775,8 +843,12 @@ var CodexCliLanguageModel = class {
775
843
  responseFormat,
776
844
  effectiveSettings
777
845
  );
846
+ this.logger.debug(
847
+ `[codex-cli] Executing Codex CLI for streaming: ${cmd} with ${args.length} arguments`
848
+ );
778
849
  const stream = new ReadableStream({
779
850
  start: (controller) => {
851
+ const startTime = Date.now();
780
852
  const child = child_process.spawn(cmd, args, { env, cwd, stdio: ["ignore", "pipe", "pipe"] });
781
853
  controller.enqueue({ type: "stream-start", warnings });
782
854
  let stderr = "";
@@ -799,12 +871,18 @@ var CodexCliLanguageModel = class {
799
871
  if (!item) return;
800
872
  if (event.type === "item.completed" && this.getItemType(item) === "assistant_message" && typeof item.text === "string") {
801
873
  accumulatedText = item.text;
874
+ this.logger.debug(
875
+ `[codex-cli] Received assistant message, length: ${item.text.length}`
876
+ );
802
877
  return;
803
878
  }
804
879
  const toolName = this.getToolName(item);
805
880
  if (!toolName) {
806
881
  return;
807
882
  }
883
+ this.logger.debug(
884
+ `[codex-cli] Tool detected: ${toolName}, item type: ${this.getItemType(item)}`
885
+ );
808
886
  const mapKey = typeof item.id === "string" && item.id.length > 0 ? item.id : crypto.randomUUID();
809
887
  let toolState = activeTools.get(mapKey);
810
888
  const latestInput = this.buildToolInputPayload(item);
@@ -823,6 +901,7 @@ var CodexCliLanguageModel = class {
823
901
  }
824
902
  }
825
903
  if (!toolState.hasEmittedCall) {
904
+ this.logger.debug(`[codex-cli] Emitting tool invocation: ${toolState.toolName}`);
826
905
  this.emitToolInvocation(
827
906
  controller,
828
907
  toolState.toolCallId,
@@ -833,6 +912,7 @@ var CodexCliLanguageModel = class {
833
912
  }
834
913
  if (event.type === "item.completed") {
835
914
  const { result, metadata } = this.buildToolResultPayload(item);
915
+ this.logger.debug(`[codex-cli] Tool completed: ${toolState.toolName}`);
836
916
  this.emitToolResult(
837
917
  controller,
838
918
  toolState.toolCallId,
@@ -856,7 +936,11 @@ var CodexCliLanguageModel = class {
856
936
  options.abortSignal.addEventListener("abort", onAbort, { once: true });
857
937
  }
858
938
  const finishStream = (code) => {
939
+ const duration = Date.now() - startTime;
859
940
  if (code !== 0) {
941
+ this.logger.error(
942
+ `[codex-cli] Stream process exited with code ${code} after ${duration}ms`
943
+ );
860
944
  controller.error(
861
945
  createAPICallError({
862
946
  message: `Codex CLI exited with code ${code}`,
@@ -868,6 +952,7 @@ var CodexCliLanguageModel = class {
868
952
  return;
869
953
  }
870
954
  if (turnFailureMessage) {
955
+ this.logger.error(`[codex-cli] Stream failed: ${turnFailureMessage}`);
871
956
  controller.error(
872
957
  createAPICallError({
873
958
  message: turnFailureMessage,
@@ -896,6 +981,12 @@ var CodexCliLanguageModel = class {
896
981
  controller.enqueue({ type: "text-end", id: textId });
897
982
  }
898
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
+ );
899
990
  controller.enqueue({
900
991
  type: "finish",
901
992
  finishReason: "stop",
@@ -910,8 +1001,10 @@ var CodexCliLanguageModel = class {
910
1001
  for (const line of lines) {
911
1002
  const event = this.parseExperimentalJsonEvent(line);
912
1003
  if (!event) continue;
1004
+ this.logger.debug(`[codex-cli] Stream event: ${event.type ?? "unknown"}`);
913
1005
  if (event.type === "thread.started" && typeof event.thread_id === "string") {
914
1006
  this.sessionId = event.thread_id;
1007
+ this.logger.debug(`[codex-cli] Stream session started: ${this.sessionId}`);
915
1008
  if (!responseMetadataSent) {
916
1009
  responseMetadataSent = true;
917
1010
  sendMetadata();
@@ -920,6 +1013,7 @@ var CodexCliLanguageModel = class {
920
1013
  }
921
1014
  if (event.type === "session.created" && typeof event.session_id === "string") {
922
1015
  this.sessionId = event.session_id;
1016
+ this.logger.debug(`[codex-cli] Stream session created: ${this.sessionId}`);
923
1017
  if (!responseMetadataSent) {
924
1018
  responseMetadataSent = true;
925
1019
  sendMetadata();
@@ -936,6 +1030,7 @@ var CodexCliLanguageModel = class {
936
1030
  if (event.type === "turn.failed") {
937
1031
  const errorText = event.error && typeof event.error.message === "string" && event.error.message || (typeof event.message === "string" ? event.message : void 0);
938
1032
  turnFailureMessage = errorText ?? turnFailureMessage ?? "Codex turn failed";
1033
+ this.logger.error(`[codex-cli] Stream turn failed: ${turnFailureMessage}`);
939
1034
  sendMetadata({ error: turnFailureMessage });
940
1035
  continue;
941
1036
  }
@@ -943,6 +1038,7 @@ var CodexCliLanguageModel = class {
943
1038
  const errorText = typeof event.message === "string" ? event.message : void 0;
944
1039
  const effective = errorText ?? "Codex error";
945
1040
  turnFailureMessage = turnFailureMessage ?? effective;
1041
+ this.logger.error(`[codex-cli] Stream error event: ${effective}`);
946
1042
  sendMetadata({ error: effective });
947
1043
  continue;
948
1044
  }
@@ -960,6 +1056,7 @@ var CodexCliLanguageModel = class {
960
1056
  }
961
1057
  };
962
1058
  child.on("error", (e) => {
1059
+ this.logger.error(`[codex-cli] Stream spawn error: ${String(e)}`);
963
1060
  if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
964
1061
  cleanupSchema();
965
1062
  controller.error(this.handleSpawnError(e, promptExcerpt));
package/dist/index.d.cts CHANGED
@@ -1,7 +1,38 @@
1
1
  import { ProviderV2, LanguageModelV2 } from '@ai-sdk/provider';
2
2
 
3
+ /**
4
+ * Logger interface for custom logging.
5
+ * Allows consumers to provide their own logging implementation
6
+ * or disable logging entirely.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const customLogger: Logger = {
11
+ * debug: (message) => myLoggingService.debug(message),
12
+ * info: (message) => myLoggingService.info(message),
13
+ * warn: (message) => myLoggingService.warn(message),
14
+ * error: (message) => myLoggingService.error(message),
15
+ * };
16
+ * ```
17
+ */
3
18
  interface Logger {
19
+ /**
20
+ * Log a debug message. Only logged when verbose mode is enabled.
21
+ * Used for detailed execution tracing and troubleshooting.
22
+ */
23
+ debug: (message: string) => void;
24
+ /**
25
+ * Log an informational message. Only logged when verbose mode is enabled.
26
+ * Used for general execution flow information.
27
+ */
28
+ info: (message: string) => void;
29
+ /**
30
+ * Log a warning message.
31
+ */
4
32
  warn: (message: string) => void;
33
+ /**
34
+ * Log an error message.
35
+ */
5
36
  error: (message: string) => void;
6
37
  }
7
38
  type ApprovalMode = 'untrusted' | 'on-failure' | 'on-request' | 'never';
package/dist/index.d.ts CHANGED
@@ -1,7 +1,38 @@
1
1
  import { ProviderV2, LanguageModelV2 } from '@ai-sdk/provider';
2
2
 
3
+ /**
4
+ * Logger interface for custom logging.
5
+ * Allows consumers to provide their own logging implementation
6
+ * or disable logging entirely.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const customLogger: Logger = {
11
+ * debug: (message) => myLoggingService.debug(message),
12
+ * info: (message) => myLoggingService.info(message),
13
+ * warn: (message) => myLoggingService.warn(message),
14
+ * error: (message) => myLoggingService.error(message),
15
+ * };
16
+ * ```
17
+ */
3
18
  interface Logger {
19
+ /**
20
+ * Log a debug message. Only logged when verbose mode is enabled.
21
+ * Used for detailed execution tracing and troubleshooting.
22
+ */
23
+ debug: (message: string) => void;
24
+ /**
25
+ * Log an informational message. Only logged when verbose mode is enabled.
26
+ * Used for general execution flow information.
27
+ */
28
+ info: (message: string) => void;
29
+ /**
30
+ * Log a warning message.
31
+ */
4
32
  warn: (message: string) => void;
33
+ /**
34
+ * Log an error message.
35
+ */
5
36
  error: (message: string) => void;
6
37
  }
7
38
  type ApprovalMode = 'untrusted' | 'on-failure' | 'on-request' | 'never';
package/dist/index.js CHANGED
@@ -12,20 +12,59 @@ import { parseProviderOptions, generateId } from '@ai-sdk/provider-utils';
12
12
 
13
13
  // src/logger.ts
14
14
  var defaultLogger = {
15
- warn: (m) => console.warn(m),
16
- error: (m) => console.error(m)
15
+ debug: (message) => console.debug(`[DEBUG] ${message}`),
16
+ info: (message) => console.info(`[INFO] ${message}`),
17
+ warn: (message) => console.warn(`[WARN] ${message}`),
18
+ error: (message) => console.error(`[ERROR] ${message}`)
17
19
  };
18
20
  var noopLogger = {
21
+ debug: () => {
22
+ },
23
+ info: () => {
24
+ },
19
25
  warn: () => {
20
26
  },
21
27
  error: () => {
22
28
  }
23
29
  };
24
30
  function getLogger(logger) {
25
- if (logger === false) return noopLogger;
26
- if (!logger) return defaultLogger;
31
+ if (logger === false) {
32
+ return noopLogger;
33
+ }
34
+ if (logger === void 0) {
35
+ return defaultLogger;
36
+ }
27
37
  return logger;
28
38
  }
39
+ function createVerboseLogger(logger, verbose = false) {
40
+ if (verbose) {
41
+ return logger;
42
+ }
43
+ return {
44
+ debug: () => {
45
+ },
46
+ // No-op when not verbose
47
+ info: () => {
48
+ },
49
+ // No-op when not verbose
50
+ warn: logger.warn.bind(logger),
51
+ error: logger.error.bind(logger)
52
+ };
53
+ }
54
+ var loggerFunctionSchema = z.object({
55
+ debug: z.any().refine((val) => typeof val === "function", {
56
+ message: "debug must be a function"
57
+ }),
58
+ info: z.any().refine((val) => typeof val === "function", {
59
+ message: "info must be a function"
60
+ }),
61
+ warn: z.any().refine((val) => typeof val === "function", {
62
+ message: "warn must be a function"
63
+ }),
64
+ error: z.any().refine((val) => typeof val === "function", {
65
+ message: "error must be a function"
66
+ })
67
+ });
29
68
  var settingsSchema = z.object({
30
69
  codexPath: z.string().optional(),
31
70
  cwd: z.string().optional(),
@@ -38,7 +77,7 @@ var settingsSchema = z.object({
38
77
  allowNpx: z.boolean().optional(),
39
78
  env: z.record(z.string(), z.string()).optional(),
40
79
  verbose: z.boolean().optional(),
41
- logger: z.any().optional(),
80
+ logger: z.union([z.literal(false), loggerFunctionSchema]).optional(),
42
81
  // NEW: Reasoning & Verbosity
43
82
  reasoningEffort: z.enum(["minimal", "low", "medium", "high"]).optional(),
44
83
  // Note: API rejects 'concise' and 'none' despite error messages claiming they're valid
@@ -230,7 +269,8 @@ var CodexCliLanguageModel = class {
230
269
  constructor(options) {
231
270
  this.modelId = options.id;
232
271
  this.settings = options.settings ?? {};
233
- this.logger = getLogger(this.settings.logger);
272
+ const baseLogger = getLogger(this.settings.logger);
273
+ this.logger = createVerboseLogger(baseLogger, this.settings.verbose ?? false);
234
274
  if (!this.modelId || this.modelId.trim() === "") {
235
275
  throw new NoSuchModelError({ modelId: this.modelId, modelType: "languageModel" });
236
276
  }
@@ -622,12 +662,16 @@ var CodexCliLanguageModel = class {
622
662
  });
623
663
  }
624
664
  async doGenerate(options) {
665
+ this.logger.debug(`[codex-cli] Starting doGenerate request with model: ${this.modelId}`);
625
666
  const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
626
667
  const promptExcerpt = promptText.slice(0, 200);
627
668
  const warnings = [
628
669
  ...this.mapWarnings(options),
629
670
  ...mappingWarnings?.map((m) => ({ type: "other", message: m })) || []
630
671
  ];
672
+ this.logger.debug(
673
+ `[codex-cli] Converted ${options.prompt.length} messages, response format: ${options.responseFormat?.type ?? "none"}`
674
+ );
631
675
  const providerOptions = await parseProviderOptions({
632
676
  provider: this.provider,
633
677
  providerOptions: options.providerOptions,
@@ -640,9 +684,13 @@ var CodexCliLanguageModel = class {
640
684
  responseFormat,
641
685
  effectiveSettings
642
686
  );
687
+ this.logger.debug(
688
+ `[codex-cli] Executing Codex CLI: ${cmd} with ${args.length} arguments, cwd: ${cwd ?? "default"}`
689
+ );
643
690
  let text = "";
644
691
  const usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
645
692
  const finishReason = "stop";
693
+ const startTime = Date.now();
646
694
  const child = spawn(cmd, args, { env, cwd, stdio: ["ignore", "pipe", "pipe"] });
647
695
  let onAbort;
648
696
  if (options.abortSignal) {
@@ -664,11 +712,14 @@ var CodexCliLanguageModel = class {
664
712
  for (const line of lines) {
665
713
  const event = this.parseExperimentalJsonEvent(line);
666
714
  if (!event) continue;
715
+ this.logger.debug(`[codex-cli] Received event type: ${event.type ?? "unknown"}`);
667
716
  if (event.type === "thread.started" && typeof event.thread_id === "string") {
668
717
  this.sessionId = event.thread_id;
718
+ this.logger.debug(`[codex-cli] Session started: ${this.sessionId}`);
669
719
  }
670
720
  if (event.type === "session.created" && typeof event.session_id === "string") {
671
721
  this.sessionId = event.session_id;
722
+ this.logger.debug(`[codex-cli] Session created: ${this.sessionId}`);
672
723
  }
673
724
  if (event.type === "turn.completed") {
674
725
  const usageEvent = this.extractUsage(event);
@@ -684,15 +735,21 @@ var CodexCliLanguageModel = class {
684
735
  if (event.type === "turn.failed") {
685
736
  const errorText = event.error && typeof event.error.message === "string" && event.error.message || (typeof event.message === "string" ? event.message : void 0);
686
737
  turnFailureMessage = errorText ?? turnFailureMessage ?? "Codex turn failed";
738
+ this.logger.error(`[codex-cli] Turn failed: ${turnFailureMessage}`);
687
739
  }
688
740
  if (event.type === "error") {
689
741
  const errorText = typeof event.message === "string" ? event.message : void 0;
690
742
  turnFailureMessage = errorText ?? turnFailureMessage ?? "Codex error";
743
+ this.logger.error(`[codex-cli] Error event: ${turnFailureMessage}`);
691
744
  }
692
745
  }
693
746
  });
694
- child.on("error", (e) => reject(this.handleSpawnError(e, promptExcerpt)));
747
+ child.on("error", (e) => {
748
+ this.logger.error(`[codex-cli] Spawn error: ${String(e)}`);
749
+ reject(this.handleSpawnError(e, promptExcerpt));
750
+ });
695
751
  child.on("close", (code) => {
752
+ const duration = Date.now() - startTime;
696
753
  if (code === 0) {
697
754
  if (turnFailureMessage) {
698
755
  reject(
@@ -704,8 +761,15 @@ var CodexCliLanguageModel = class {
704
761
  );
705
762
  return;
706
763
  }
764
+ this.logger.info(
765
+ `[codex-cli] Request completed - Session: ${this.sessionId ?? "N/A"}, Duration: ${duration}ms, Tokens: ${usage.totalTokens}`
766
+ );
767
+ this.logger.debug(
768
+ `[codex-cli] Token usage - Input: ${usage.inputTokens}, Output: ${usage.outputTokens}, Total: ${usage.totalTokens}`
769
+ );
707
770
  resolve();
708
771
  } else {
772
+ this.logger.error(`[codex-cli] Process exited with code ${code} after ${duration}ms`);
709
773
  reject(
710
774
  createAPICallError({
711
775
  message: `Codex CLI exited with code ${code}`,
@@ -754,12 +818,16 @@ var CodexCliLanguageModel = class {
754
818
  };
755
819
  }
756
820
  async doStream(options) {
821
+ this.logger.debug(`[codex-cli] Starting doStream request with model: ${this.modelId}`);
757
822
  const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
758
823
  const promptExcerpt = promptText.slice(0, 200);
759
824
  const warnings = [
760
825
  ...this.mapWarnings(options),
761
826
  ...mappingWarnings?.map((m) => ({ type: "other", message: m })) || []
762
827
  ];
828
+ this.logger.debug(
829
+ `[codex-cli] Converted ${options.prompt.length} messages for streaming, response format: ${options.responseFormat?.type ?? "none"}`
830
+ );
763
831
  const providerOptions = await parseProviderOptions({
764
832
  provider: this.provider,
765
833
  providerOptions: options.providerOptions,
@@ -772,8 +840,12 @@ var CodexCliLanguageModel = class {
772
840
  responseFormat,
773
841
  effectiveSettings
774
842
  );
843
+ this.logger.debug(
844
+ `[codex-cli] Executing Codex CLI for streaming: ${cmd} with ${args.length} arguments`
845
+ );
775
846
  const stream = new ReadableStream({
776
847
  start: (controller) => {
848
+ const startTime = Date.now();
777
849
  const child = spawn(cmd, args, { env, cwd, stdio: ["ignore", "pipe", "pipe"] });
778
850
  controller.enqueue({ type: "stream-start", warnings });
779
851
  let stderr = "";
@@ -796,12 +868,18 @@ var CodexCliLanguageModel = class {
796
868
  if (!item) return;
797
869
  if (event.type === "item.completed" && this.getItemType(item) === "assistant_message" && typeof item.text === "string") {
798
870
  accumulatedText = item.text;
871
+ this.logger.debug(
872
+ `[codex-cli] Received assistant message, length: ${item.text.length}`
873
+ );
799
874
  return;
800
875
  }
801
876
  const toolName = this.getToolName(item);
802
877
  if (!toolName) {
803
878
  return;
804
879
  }
880
+ this.logger.debug(
881
+ `[codex-cli] Tool detected: ${toolName}, item type: ${this.getItemType(item)}`
882
+ );
805
883
  const mapKey = typeof item.id === "string" && item.id.length > 0 ? item.id : randomUUID();
806
884
  let toolState = activeTools.get(mapKey);
807
885
  const latestInput = this.buildToolInputPayload(item);
@@ -820,6 +898,7 @@ var CodexCliLanguageModel = class {
820
898
  }
821
899
  }
822
900
  if (!toolState.hasEmittedCall) {
901
+ this.logger.debug(`[codex-cli] Emitting tool invocation: ${toolState.toolName}`);
823
902
  this.emitToolInvocation(
824
903
  controller,
825
904
  toolState.toolCallId,
@@ -830,6 +909,7 @@ var CodexCliLanguageModel = class {
830
909
  }
831
910
  if (event.type === "item.completed") {
832
911
  const { result, metadata } = this.buildToolResultPayload(item);
912
+ this.logger.debug(`[codex-cli] Tool completed: ${toolState.toolName}`);
833
913
  this.emitToolResult(
834
914
  controller,
835
915
  toolState.toolCallId,
@@ -853,7 +933,11 @@ var CodexCliLanguageModel = class {
853
933
  options.abortSignal.addEventListener("abort", onAbort, { once: true });
854
934
  }
855
935
  const finishStream = (code) => {
936
+ const duration = Date.now() - startTime;
856
937
  if (code !== 0) {
938
+ this.logger.error(
939
+ `[codex-cli] Stream process exited with code ${code} after ${duration}ms`
940
+ );
857
941
  controller.error(
858
942
  createAPICallError({
859
943
  message: `Codex CLI exited with code ${code}`,
@@ -865,6 +949,7 @@ var CodexCliLanguageModel = class {
865
949
  return;
866
950
  }
867
951
  if (turnFailureMessage) {
952
+ this.logger.error(`[codex-cli] Stream failed: ${turnFailureMessage}`);
868
953
  controller.error(
869
954
  createAPICallError({
870
955
  message: turnFailureMessage,
@@ -893,6 +978,12 @@ var CodexCliLanguageModel = class {
893
978
  controller.enqueue({ type: "text-end", id: textId });
894
979
  }
895
980
  const usageSummary = lastUsage ?? { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
981
+ this.logger.info(
982
+ `[codex-cli] Stream completed - Session: ${this.sessionId ?? "N/A"}, Duration: ${duration}ms, Tokens: ${usageSummary.totalTokens}`
983
+ );
984
+ this.logger.debug(
985
+ `[codex-cli] Token usage - Input: ${usageSummary.inputTokens}, Output: ${usageSummary.outputTokens}, Total: ${usageSummary.totalTokens}`
986
+ );
896
987
  controller.enqueue({
897
988
  type: "finish",
898
989
  finishReason: "stop",
@@ -907,8 +998,10 @@ var CodexCliLanguageModel = class {
907
998
  for (const line of lines) {
908
999
  const event = this.parseExperimentalJsonEvent(line);
909
1000
  if (!event) continue;
1001
+ this.logger.debug(`[codex-cli] Stream event: ${event.type ?? "unknown"}`);
910
1002
  if (event.type === "thread.started" && typeof event.thread_id === "string") {
911
1003
  this.sessionId = event.thread_id;
1004
+ this.logger.debug(`[codex-cli] Stream session started: ${this.sessionId}`);
912
1005
  if (!responseMetadataSent) {
913
1006
  responseMetadataSent = true;
914
1007
  sendMetadata();
@@ -917,6 +1010,7 @@ var CodexCliLanguageModel = class {
917
1010
  }
918
1011
  if (event.type === "session.created" && typeof event.session_id === "string") {
919
1012
  this.sessionId = event.session_id;
1013
+ this.logger.debug(`[codex-cli] Stream session created: ${this.sessionId}`);
920
1014
  if (!responseMetadataSent) {
921
1015
  responseMetadataSent = true;
922
1016
  sendMetadata();
@@ -933,6 +1027,7 @@ var CodexCliLanguageModel = class {
933
1027
  if (event.type === "turn.failed") {
934
1028
  const errorText = event.error && typeof event.error.message === "string" && event.error.message || (typeof event.message === "string" ? event.message : void 0);
935
1029
  turnFailureMessage = errorText ?? turnFailureMessage ?? "Codex turn failed";
1030
+ this.logger.error(`[codex-cli] Stream turn failed: ${turnFailureMessage}`);
936
1031
  sendMetadata({ error: turnFailureMessage });
937
1032
  continue;
938
1033
  }
@@ -940,6 +1035,7 @@ var CodexCliLanguageModel = class {
940
1035
  const errorText = typeof event.message === "string" ? event.message : void 0;
941
1036
  const effective = errorText ?? "Codex error";
942
1037
  turnFailureMessage = turnFailureMessage ?? effective;
1038
+ this.logger.error(`[codex-cli] Stream error event: ${effective}`);
943
1039
  sendMetadata({ error: effective });
944
1040
  continue;
945
1041
  }
@@ -957,6 +1053,7 @@ var CodexCliLanguageModel = class {
957
1053
  }
958
1054
  };
959
1055
  child.on("error", (e) => {
1056
+ this.logger.error(`[codex-cli] Stream spawn error: ${String(e)}`);
960
1057
  if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
961
1058
  cleanupSchema();
962
1059
  controller.error(this.handleSpawnError(e, promptExcerpt));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-sdk-provider-codex-cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "AI SDK v5 provider for OpenAI Codex CLI with native JSON Schema support",
5
5
  "keywords": [
6
6
  "ai-sdk",