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 +124 -0
- package/dist/index.cjs +254 -22
- package/dist/index.d.cts +154 -1
- package/dist/index.d.ts +154 -1
- package/dist/index.js +254 -22
- package/package.json +1 -1
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
|
-
|
|
19
|
-
|
|
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)
|
|
29
|
-
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
311
|
+
if (settings.fullAuto) {
|
|
219
312
|
args.push("--full-auto");
|
|
220
|
-
} else if (
|
|
313
|
+
} else if (settings.dangerouslyBypassApprovalsAndSandbox) {
|
|
221
314
|
args.push("--dangerously-bypass-approvals-and-sandbox");
|
|
222
315
|
} else {
|
|
223
|
-
const approval =
|
|
316
|
+
const approval = settings.approvalMode ?? "on-failure";
|
|
224
317
|
args.push("-c", `approval_policy=${approval}`);
|
|
225
|
-
const sandbox =
|
|
318
|
+
const sandbox = settings.sandboxMode ?? "workspace-write";
|
|
226
319
|
args.push("-c", `sandbox_mode=${sandbox}`);
|
|
227
320
|
}
|
|
228
|
-
if (
|
|
321
|
+
if (settings.skipGitRepoCheck !== false) {
|
|
229
322
|
args.push("--skip-git-repo-check");
|
|
230
323
|
}
|
|
231
|
-
if (
|
|
232
|
-
args.push("
|
|
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
|
-
...
|
|
378
|
+
...settings.env || {},
|
|
257
379
|
RUST_LOG: process.env.RUST_LOG || "error"
|
|
258
380
|
};
|
|
259
|
-
let lastMessagePath =
|
|
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:
|
|
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) =>
|
|
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));
|