ai-sdk-provider-codex-cli 0.1.0 → 0.3.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 +85 -13
- package/dist/index.cjs +437 -97
- package/dist/index.d.cts +11 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.js +439 -99
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -13,22 +13,6 @@ var zod = require('zod');
|
|
|
13
13
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
14
14
|
// src/codex-cli-provider.ts
|
|
15
15
|
|
|
16
|
-
// src/extract-json.ts
|
|
17
|
-
function extractJson(text) {
|
|
18
|
-
const start = text.indexOf("{");
|
|
19
|
-
if (start === -1) return text;
|
|
20
|
-
let depth = 0;
|
|
21
|
-
for (let i = start; i < text.length; i++) {
|
|
22
|
-
const ch = text[i];
|
|
23
|
-
if (ch === "{") depth++;
|
|
24
|
-
else if (ch === "}") {
|
|
25
|
-
depth--;
|
|
26
|
-
if (depth === 0) return text.slice(start, i + 1);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return text;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
16
|
// src/logger.ts
|
|
33
17
|
var defaultLogger = {
|
|
34
18
|
warn: (m) => console.warn(m),
|
|
@@ -108,7 +92,7 @@ function isToolItem(p) {
|
|
|
108
92
|
if (out.type === "text" && typeof out.value !== "string") return false;
|
|
109
93
|
return true;
|
|
110
94
|
}
|
|
111
|
-
function mapMessagesToPrompt(prompt
|
|
95
|
+
function mapMessagesToPrompt(prompt) {
|
|
112
96
|
const warnings = [];
|
|
113
97
|
const parts = [];
|
|
114
98
|
let systemText;
|
|
@@ -151,17 +135,6 @@ function mapMessagesToPrompt(prompt, mode = { type: "regular" }, jsonSchema) {
|
|
|
151
135
|
let promptText = "";
|
|
152
136
|
if (systemText) promptText += systemText + "\n\n";
|
|
153
137
|
promptText += parts.join("\n\n");
|
|
154
|
-
if (mode.type === "object-json" && jsonSchema) {
|
|
155
|
-
const schemaStr = JSON.stringify(jsonSchema, null, 2);
|
|
156
|
-
promptText = `CRITICAL: You MUST respond with ONLY a JSON object. NO other text.
|
|
157
|
-
Your response MUST start with { and end with }
|
|
158
|
-
The JSON MUST match this EXACT schema:
|
|
159
|
-
${schemaStr}
|
|
160
|
-
|
|
161
|
-
Now, based on the following conversation, generate ONLY the JSON object:
|
|
162
|
-
|
|
163
|
-
${promptText}`;
|
|
164
|
-
}
|
|
165
138
|
return { promptText, ...warnings.length ? { warnings } : {} };
|
|
166
139
|
}
|
|
167
140
|
function createAPICallError({
|
|
@@ -214,7 +187,7 @@ var CodexCliLanguageModel = class {
|
|
|
214
187
|
defaultObjectGenerationMode = "json";
|
|
215
188
|
supportsImageUrls = false;
|
|
216
189
|
supportedUrls = {};
|
|
217
|
-
supportsStructuredOutputs =
|
|
190
|
+
supportsStructuredOutputs = true;
|
|
218
191
|
modelId;
|
|
219
192
|
settings;
|
|
220
193
|
logger;
|
|
@@ -229,9 +202,19 @@ var CodexCliLanguageModel = class {
|
|
|
229
202
|
const warn = validateModelId(this.modelId);
|
|
230
203
|
if (warn) this.logger.warn(`Codex CLI model: ${warn}`);
|
|
231
204
|
}
|
|
232
|
-
|
|
205
|
+
// Codex JSONL items use `type` for the item discriminator, but some
|
|
206
|
+
// earlier fixtures (and defensive parsing) might still surface `item_type`.
|
|
207
|
+
// This helper returns whichever is present.
|
|
208
|
+
getItemType(item) {
|
|
209
|
+
if (!item) return void 0;
|
|
210
|
+
const data = item;
|
|
211
|
+
const legacy = typeof data.item_type === "string" ? data.item_type : void 0;
|
|
212
|
+
const current = typeof data.type === "string" ? data.type : void 0;
|
|
213
|
+
return legacy ?? current;
|
|
214
|
+
}
|
|
215
|
+
buildArgs(promptText, responseFormat) {
|
|
233
216
|
const base = resolveCodexPath(this.settings.codexPath, this.settings.allowNpx);
|
|
234
|
-
const args = [...base.args, "exec", "--json"];
|
|
217
|
+
const args = [...base.args, "exec", "--experimental-json"];
|
|
235
218
|
if (this.settings.fullAuto) {
|
|
236
219
|
args.push("--full-auto");
|
|
237
220
|
} else if (this.settings.dangerouslyBypassApprovalsAndSandbox) {
|
|
@@ -251,6 +234,22 @@ var CodexCliLanguageModel = class {
|
|
|
251
234
|
if (this.modelId) {
|
|
252
235
|
args.push("-m", this.modelId);
|
|
253
236
|
}
|
|
237
|
+
let schemaPath;
|
|
238
|
+
if (responseFormat?.type === "json" && responseFormat.schema) {
|
|
239
|
+
const schema = typeof responseFormat.schema === "object" ? responseFormat.schema : {};
|
|
240
|
+
const sanitizedSchema = this.sanitizeJsonSchema(schema);
|
|
241
|
+
const hasProperties = Object.keys(sanitizedSchema).length > 0;
|
|
242
|
+
if (hasProperties) {
|
|
243
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "codex-schema-"));
|
|
244
|
+
schemaPath = path.join(dir, "schema.json");
|
|
245
|
+
const schemaWithAdditional = {
|
|
246
|
+
...sanitizedSchema,
|
|
247
|
+
additionalProperties: false
|
|
248
|
+
};
|
|
249
|
+
fs.writeFileSync(schemaPath, JSON.stringify(schemaWithAdditional, null, 2));
|
|
250
|
+
args.push("--output-schema", schemaPath);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
254
253
|
args.push(promptText);
|
|
255
254
|
const env = {
|
|
256
255
|
...process.env,
|
|
@@ -263,7 +262,34 @@ var CodexCliLanguageModel = class {
|
|
|
263
262
|
lastMessagePath = path.join(dir, "last-message.txt");
|
|
264
263
|
}
|
|
265
264
|
args.push("--output-last-message", lastMessagePath);
|
|
266
|
-
return { cmd: base.cmd, args, env, cwd: this.settings.cwd, lastMessagePath };
|
|
265
|
+
return { cmd: base.cmd, args, env, cwd: this.settings.cwd, lastMessagePath, schemaPath };
|
|
266
|
+
}
|
|
267
|
+
sanitizeJsonSchema(value) {
|
|
268
|
+
if (typeof value !== "object" || value === null) {
|
|
269
|
+
return value;
|
|
270
|
+
}
|
|
271
|
+
if (Array.isArray(value)) {
|
|
272
|
+
return value.map((item) => this.sanitizeJsonSchema(item));
|
|
273
|
+
}
|
|
274
|
+
const obj = value;
|
|
275
|
+
const result = {};
|
|
276
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
277
|
+
if (key === "properties" && typeof val === "object" && val !== null && !Array.isArray(val)) {
|
|
278
|
+
const props = val;
|
|
279
|
+
const sanitizedProps = {};
|
|
280
|
+
for (const [propName, propSchema] of Object.entries(props)) {
|
|
281
|
+
sanitizedProps[propName] = this.sanitizeJsonSchema(propSchema);
|
|
282
|
+
}
|
|
283
|
+
result[key] = sanitizedProps;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (key === "$schema" || key === "$id" || key === "$ref" || key === "$defs" || key === "definitions" || key === "title" || key === "examples" || key === "default" || key === "format" || // OpenAI strict mode doesn't support format
|
|
287
|
+
key === "pattern") {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
result[key] = this.sanitizeJsonSchema(val);
|
|
291
|
+
}
|
|
292
|
+
return result;
|
|
267
293
|
}
|
|
268
294
|
mapWarnings(options) {
|
|
269
295
|
const unsupported = [];
|
|
@@ -284,13 +310,185 @@ var CodexCliLanguageModel = class {
|
|
|
284
310
|
add(options.seed, "seed");
|
|
285
311
|
return unsupported;
|
|
286
312
|
}
|
|
287
|
-
|
|
313
|
+
parseExperimentalJsonEvent(line) {
|
|
288
314
|
try {
|
|
289
315
|
return JSON.parse(line);
|
|
290
316
|
} catch {
|
|
291
317
|
return void 0;
|
|
292
318
|
}
|
|
293
319
|
}
|
|
320
|
+
extractUsage(evt) {
|
|
321
|
+
const reported = evt.usage;
|
|
322
|
+
if (!reported) return void 0;
|
|
323
|
+
const inputTokens = reported.input_tokens ?? 0;
|
|
324
|
+
const outputTokens = reported.output_tokens ?? 0;
|
|
325
|
+
const cachedInputTokens = reported.cached_input_tokens ?? 0;
|
|
326
|
+
return {
|
|
327
|
+
inputTokens,
|
|
328
|
+
outputTokens,
|
|
329
|
+
// totalTokens should not double-count cached tokens; track cached separately
|
|
330
|
+
totalTokens: inputTokens + outputTokens,
|
|
331
|
+
cachedInputTokens
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
getToolName(item) {
|
|
335
|
+
if (!item) return void 0;
|
|
336
|
+
const itemType = this.getItemType(item);
|
|
337
|
+
switch (itemType) {
|
|
338
|
+
case "command_execution":
|
|
339
|
+
return "exec";
|
|
340
|
+
case "file_change":
|
|
341
|
+
return "patch";
|
|
342
|
+
case "mcp_tool_call": {
|
|
343
|
+
const tool = item.tool;
|
|
344
|
+
if (typeof tool === "string" && tool.length > 0) return tool;
|
|
345
|
+
return "mcp_tool";
|
|
346
|
+
}
|
|
347
|
+
case "web_search":
|
|
348
|
+
return "web_search";
|
|
349
|
+
default:
|
|
350
|
+
return void 0;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
buildToolInputPayload(item) {
|
|
354
|
+
if (!item) return void 0;
|
|
355
|
+
const data = item;
|
|
356
|
+
switch (this.getItemType(item)) {
|
|
357
|
+
case "command_execution": {
|
|
358
|
+
const payload = {};
|
|
359
|
+
if (typeof data.command === "string") payload.command = data.command;
|
|
360
|
+
if (typeof data.status === "string") payload.status = data.status;
|
|
361
|
+
if (typeof data.cwd === "string") payload.cwd = data.cwd;
|
|
362
|
+
return Object.keys(payload).length ? payload : void 0;
|
|
363
|
+
}
|
|
364
|
+
case "file_change": {
|
|
365
|
+
const payload = {};
|
|
366
|
+
if (Array.isArray(data.changes)) payload.changes = data.changes;
|
|
367
|
+
if (typeof data.status === "string") payload.status = data.status;
|
|
368
|
+
return Object.keys(payload).length ? payload : void 0;
|
|
369
|
+
}
|
|
370
|
+
case "mcp_tool_call": {
|
|
371
|
+
const payload = {};
|
|
372
|
+
if (typeof data.server === "string") payload.server = data.server;
|
|
373
|
+
if (typeof data.tool === "string") payload.tool = data.tool;
|
|
374
|
+
if (typeof data.status === "string") payload.status = data.status;
|
|
375
|
+
if (data.arguments !== void 0) payload.arguments = data.arguments;
|
|
376
|
+
return Object.keys(payload).length ? payload : void 0;
|
|
377
|
+
}
|
|
378
|
+
case "web_search": {
|
|
379
|
+
const payload = {};
|
|
380
|
+
if (typeof data.query === "string") payload.query = data.query;
|
|
381
|
+
return Object.keys(payload).length ? payload : void 0;
|
|
382
|
+
}
|
|
383
|
+
default:
|
|
384
|
+
return void 0;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
buildToolResultPayload(item) {
|
|
388
|
+
if (!item) return { result: {} };
|
|
389
|
+
const data = item;
|
|
390
|
+
const metadata = {};
|
|
391
|
+
const itemType = this.getItemType(item);
|
|
392
|
+
if (typeof itemType === "string") metadata.itemType = itemType;
|
|
393
|
+
if (typeof item.id === "string") metadata.itemId = item.id;
|
|
394
|
+
if (typeof data.status === "string") metadata.status = data.status;
|
|
395
|
+
const buildResult = (result) => ({
|
|
396
|
+
result,
|
|
397
|
+
metadata: Object.keys(metadata).length ? metadata : void 0
|
|
398
|
+
});
|
|
399
|
+
switch (itemType) {
|
|
400
|
+
case "command_execution": {
|
|
401
|
+
const result = {};
|
|
402
|
+
if (typeof data.command === "string") result.command = data.command;
|
|
403
|
+
if (typeof data.aggregated_output === "string")
|
|
404
|
+
result.aggregatedOutput = data.aggregated_output;
|
|
405
|
+
if (typeof data.exit_code === "number") result.exitCode = data.exit_code;
|
|
406
|
+
if (typeof data.status === "string") result.status = data.status;
|
|
407
|
+
return buildResult(result);
|
|
408
|
+
}
|
|
409
|
+
case "file_change": {
|
|
410
|
+
const result = {};
|
|
411
|
+
if (Array.isArray(data.changes)) result.changes = data.changes;
|
|
412
|
+
if (typeof data.status === "string") result.status = data.status;
|
|
413
|
+
return buildResult(result);
|
|
414
|
+
}
|
|
415
|
+
case "mcp_tool_call": {
|
|
416
|
+
const result = {};
|
|
417
|
+
if (typeof data.server === "string") {
|
|
418
|
+
result.server = data.server;
|
|
419
|
+
metadata.server = data.server;
|
|
420
|
+
}
|
|
421
|
+
if (typeof data.tool === "string") result.tool = data.tool;
|
|
422
|
+
if (typeof data.status === "string") result.status = data.status;
|
|
423
|
+
if (data.result !== void 0) result.result = data.result;
|
|
424
|
+
if (data.error !== void 0) result.error = data.error;
|
|
425
|
+
return buildResult(result);
|
|
426
|
+
}
|
|
427
|
+
case "web_search": {
|
|
428
|
+
const result = {};
|
|
429
|
+
if (typeof data.query === "string") result.query = data.query;
|
|
430
|
+
if (typeof data.status === "string") result.status = data.status;
|
|
431
|
+
return buildResult(result);
|
|
432
|
+
}
|
|
433
|
+
default: {
|
|
434
|
+
const result = { ...data };
|
|
435
|
+
return buildResult(result);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
safeStringify(value) {
|
|
440
|
+
if (value === void 0) return "";
|
|
441
|
+
if (typeof value === "string") return value;
|
|
442
|
+
try {
|
|
443
|
+
return JSON.stringify(value);
|
|
444
|
+
} catch {
|
|
445
|
+
return "";
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
emitToolInvocation(controller, toolCallId, toolName, inputPayload) {
|
|
449
|
+
const inputString = this.safeStringify(inputPayload);
|
|
450
|
+
controller.enqueue({ type: "tool-input-start", id: toolCallId, toolName });
|
|
451
|
+
if (inputString) {
|
|
452
|
+
controller.enqueue({ type: "tool-input-delta", id: toolCallId, delta: inputString });
|
|
453
|
+
}
|
|
454
|
+
controller.enqueue({ type: "tool-input-end", id: toolCallId });
|
|
455
|
+
controller.enqueue({
|
|
456
|
+
type: "tool-call",
|
|
457
|
+
toolCallId,
|
|
458
|
+
toolName,
|
|
459
|
+
input: inputString,
|
|
460
|
+
providerExecuted: true
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
emitToolResult(controller, toolCallId, toolName, item, resultPayload, metadata) {
|
|
464
|
+
const providerMetadataEntries = {
|
|
465
|
+
...metadata ?? {}
|
|
466
|
+
};
|
|
467
|
+
const itemType = this.getItemType(item);
|
|
468
|
+
if (itemType && providerMetadataEntries.itemType === void 0) {
|
|
469
|
+
providerMetadataEntries.itemType = itemType;
|
|
470
|
+
}
|
|
471
|
+
if (item.id && providerMetadataEntries.itemId === void 0) {
|
|
472
|
+
providerMetadataEntries.itemId = item.id;
|
|
473
|
+
}
|
|
474
|
+
let isError;
|
|
475
|
+
if (itemType === "command_execution") {
|
|
476
|
+
const data = item;
|
|
477
|
+
const exitCode = typeof data.exit_code === "number" ? data.exit_code : void 0;
|
|
478
|
+
const status = typeof data.status === "string" ? data.status : void 0;
|
|
479
|
+
if (exitCode !== void 0 && exitCode !== 0 || status === "failed") {
|
|
480
|
+
isError = true;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
controller.enqueue({
|
|
484
|
+
type: "tool-result",
|
|
485
|
+
toolCallId,
|
|
486
|
+
toolName,
|
|
487
|
+
result: resultPayload ?? {},
|
|
488
|
+
...isError ? { isError: true } : {},
|
|
489
|
+
...Object.keys(providerMetadataEntries).length ? { providerMetadata: { "codex-cli": providerMetadataEntries } } : {}
|
|
490
|
+
});
|
|
491
|
+
}
|
|
294
492
|
handleSpawnError(err, promptExcerpt) {
|
|
295
493
|
const e = err && typeof err === "object" ? err : void 0;
|
|
296
494
|
const message = String((e?.message ?? err) || "Failed to run Codex CLI");
|
|
@@ -306,18 +504,17 @@ var CodexCliLanguageModel = class {
|
|
|
306
504
|
});
|
|
307
505
|
}
|
|
308
506
|
async doGenerate(options) {
|
|
309
|
-
const
|
|
310
|
-
const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(
|
|
311
|
-
options.prompt,
|
|
312
|
-
mode,
|
|
313
|
-
options.responseFormat?.type === "json" ? options.responseFormat.schema : void 0
|
|
314
|
-
);
|
|
507
|
+
const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
|
|
315
508
|
const promptExcerpt = promptText.slice(0, 200);
|
|
316
509
|
const warnings = [
|
|
317
510
|
...this.mapWarnings(options),
|
|
318
511
|
...mappingWarnings?.map((m) => ({ type: "other", message: m })) || []
|
|
319
512
|
];
|
|
320
|
-
const {
|
|
513
|
+
const responseFormat = options.responseFormat?.type === "json" ? { type: "json", schema: options.responseFormat.schema } : void 0;
|
|
514
|
+
const { cmd, args, env, cwd, lastMessagePath, schemaPath } = this.buildArgs(
|
|
515
|
+
promptText,
|
|
516
|
+
responseFormat
|
|
517
|
+
);
|
|
321
518
|
let text = "";
|
|
322
519
|
const usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
323
520
|
const finishReason = "stop";
|
|
@@ -334,27 +531,56 @@ var CodexCliLanguageModel = class {
|
|
|
334
531
|
try {
|
|
335
532
|
await new Promise((resolve, reject) => {
|
|
336
533
|
let stderr = "";
|
|
534
|
+
let turnFailureMessage;
|
|
337
535
|
child.stderr.on("data", (d) => stderr += String(d));
|
|
338
536
|
child.stdout.setEncoding("utf8");
|
|
339
537
|
child.stdout.on("data", (chunk) => {
|
|
340
538
|
const lines = chunk.split(/\r?\n/).filter(Boolean);
|
|
341
539
|
for (const line of lines) {
|
|
342
|
-
const
|
|
343
|
-
if (!
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
540
|
+
const event = this.parseExperimentalJsonEvent(line);
|
|
541
|
+
if (!event) continue;
|
|
542
|
+
if (event.type === "thread.started" && typeof event.thread_id === "string") {
|
|
543
|
+
this.sessionId = event.thread_id;
|
|
544
|
+
}
|
|
545
|
+
if (event.type === "session.created" && typeof event.session_id === "string") {
|
|
546
|
+
this.sessionId = event.session_id;
|
|
547
|
+
}
|
|
548
|
+
if (event.type === "turn.completed") {
|
|
549
|
+
const usageEvent = this.extractUsage(event);
|
|
550
|
+
if (usageEvent) {
|
|
551
|
+
usage.inputTokens = usageEvent.inputTokens;
|
|
552
|
+
usage.outputTokens = usageEvent.outputTokens;
|
|
553
|
+
usage.totalTokens = usageEvent.totalTokens;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (event.type === "item.completed" && this.getItemType(event.item) === "assistant_message" && typeof event.item?.text === "string") {
|
|
557
|
+
text = event.item.text;
|
|
558
|
+
}
|
|
559
|
+
if (event.type === "turn.failed") {
|
|
560
|
+
const errorText = event.error && typeof event.error.message === "string" && event.error.message || (typeof event.message === "string" ? event.message : void 0);
|
|
561
|
+
turnFailureMessage = errorText ?? turnFailureMessage ?? "Codex turn failed";
|
|
562
|
+
}
|
|
563
|
+
if (event.type === "error") {
|
|
564
|
+
const errorText = typeof event.message === "string" ? event.message : void 0;
|
|
565
|
+
turnFailureMessage = errorText ?? turnFailureMessage ?? "Codex error";
|
|
351
566
|
}
|
|
352
567
|
}
|
|
353
568
|
});
|
|
354
569
|
child.on("error", (e) => reject(this.handleSpawnError(e, promptExcerpt)));
|
|
355
570
|
child.on("close", (code) => {
|
|
356
|
-
if (code === 0)
|
|
357
|
-
|
|
571
|
+
if (code === 0) {
|
|
572
|
+
if (turnFailureMessage) {
|
|
573
|
+
reject(
|
|
574
|
+
createAPICallError({
|
|
575
|
+
message: turnFailureMessage,
|
|
576
|
+
stderr,
|
|
577
|
+
promptExcerpt
|
|
578
|
+
})
|
|
579
|
+
);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
resolve();
|
|
583
|
+
} else {
|
|
358
584
|
reject(
|
|
359
585
|
createAPICallError({
|
|
360
586
|
message: `Codex CLI exited with code ${code}`,
|
|
@@ -363,10 +589,18 @@ var CodexCliLanguageModel = class {
|
|
|
363
589
|
promptExcerpt
|
|
364
590
|
})
|
|
365
591
|
);
|
|
592
|
+
}
|
|
366
593
|
});
|
|
367
594
|
});
|
|
368
595
|
} finally {
|
|
369
596
|
if (options.abortSignal && onAbort) options.abortSignal.removeEventListener("abort", onAbort);
|
|
597
|
+
if (schemaPath) {
|
|
598
|
+
try {
|
|
599
|
+
const schemaDir = path.dirname(schemaPath);
|
|
600
|
+
fs.rmSync(schemaDir, { recursive: true, force: true });
|
|
601
|
+
} catch {
|
|
602
|
+
}
|
|
603
|
+
}
|
|
370
604
|
}
|
|
371
605
|
if (!text && lastMessagePath) {
|
|
372
606
|
try {
|
|
@@ -381,9 +615,6 @@ var CodexCliLanguageModel = class {
|
|
|
381
615
|
} catch {
|
|
382
616
|
}
|
|
383
617
|
}
|
|
384
|
-
if (options.responseFormat?.type === "json" && text) {
|
|
385
|
-
text = extractJson(text);
|
|
386
|
-
}
|
|
387
618
|
const content = [{ type: "text", text }];
|
|
388
619
|
return {
|
|
389
620
|
content,
|
|
@@ -398,24 +629,86 @@ var CodexCliLanguageModel = class {
|
|
|
398
629
|
};
|
|
399
630
|
}
|
|
400
631
|
async doStream(options) {
|
|
401
|
-
const
|
|
402
|
-
const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(
|
|
403
|
-
options.prompt,
|
|
404
|
-
mode,
|
|
405
|
-
options.responseFormat?.type === "json" ? options.responseFormat.schema : void 0
|
|
406
|
-
);
|
|
632
|
+
const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
|
|
407
633
|
const promptExcerpt = promptText.slice(0, 200);
|
|
408
634
|
const warnings = [
|
|
409
635
|
...this.mapWarnings(options),
|
|
410
636
|
...mappingWarnings?.map((m) => ({ type: "other", message: m })) || []
|
|
411
637
|
];
|
|
412
|
-
const {
|
|
638
|
+
const responseFormat = options.responseFormat?.type === "json" ? { type: "json", schema: options.responseFormat.schema } : void 0;
|
|
639
|
+
const { cmd, args, env, cwd, lastMessagePath, schemaPath } = this.buildArgs(
|
|
640
|
+
promptText,
|
|
641
|
+
responseFormat
|
|
642
|
+
);
|
|
413
643
|
const stream = new ReadableStream({
|
|
414
644
|
start: (controller) => {
|
|
415
645
|
const child = child_process.spawn(cmd, args, { env, cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
416
646
|
controller.enqueue({ type: "stream-start", warnings });
|
|
417
647
|
let stderr = "";
|
|
418
648
|
let accumulatedText = "";
|
|
649
|
+
const activeTools = /* @__PURE__ */ new Map();
|
|
650
|
+
let responseMetadataSent = false;
|
|
651
|
+
let lastUsage;
|
|
652
|
+
let turnFailureMessage;
|
|
653
|
+
const sendMetadata = (meta = {}) => {
|
|
654
|
+
controller.enqueue({
|
|
655
|
+
type: "response-metadata",
|
|
656
|
+
id: crypto.randomUUID(),
|
|
657
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
658
|
+
modelId: this.modelId,
|
|
659
|
+
...Object.keys(meta).length ? { providerMetadata: { "codex-cli": meta } } : {}
|
|
660
|
+
});
|
|
661
|
+
};
|
|
662
|
+
const handleItemEvent = (event) => {
|
|
663
|
+
const item = event.item;
|
|
664
|
+
if (!item) return;
|
|
665
|
+
if (event.type === "item.completed" && this.getItemType(item) === "assistant_message" && typeof item.text === "string") {
|
|
666
|
+
accumulatedText = item.text;
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
const toolName = this.getToolName(item);
|
|
670
|
+
if (!toolName) {
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const mapKey = typeof item.id === "string" && item.id.length > 0 ? item.id : crypto.randomUUID();
|
|
674
|
+
let toolState = activeTools.get(mapKey);
|
|
675
|
+
const latestInput = this.buildToolInputPayload(item);
|
|
676
|
+
if (!toolState) {
|
|
677
|
+
toolState = {
|
|
678
|
+
toolCallId: mapKey,
|
|
679
|
+
toolName,
|
|
680
|
+
inputPayload: latestInput,
|
|
681
|
+
hasEmittedCall: false
|
|
682
|
+
};
|
|
683
|
+
activeTools.set(mapKey, toolState);
|
|
684
|
+
} else {
|
|
685
|
+
toolState.toolName = toolName;
|
|
686
|
+
if (latestInput !== void 0) {
|
|
687
|
+
toolState.inputPayload = latestInput;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (!toolState.hasEmittedCall) {
|
|
691
|
+
this.emitToolInvocation(
|
|
692
|
+
controller,
|
|
693
|
+
toolState.toolCallId,
|
|
694
|
+
toolState.toolName,
|
|
695
|
+
toolState.inputPayload
|
|
696
|
+
);
|
|
697
|
+
toolState.hasEmittedCall = true;
|
|
698
|
+
}
|
|
699
|
+
if (event.type === "item.completed") {
|
|
700
|
+
const { result, metadata } = this.buildToolResultPayload(item);
|
|
701
|
+
this.emitToolResult(
|
|
702
|
+
controller,
|
|
703
|
+
toolState.toolCallId,
|
|
704
|
+
toolState.toolName,
|
|
705
|
+
item,
|
|
706
|
+
result,
|
|
707
|
+
metadata
|
|
708
|
+
);
|
|
709
|
+
activeTools.delete(mapKey);
|
|
710
|
+
}
|
|
711
|
+
};
|
|
419
712
|
const onAbort = () => {
|
|
420
713
|
child.kill("SIGTERM");
|
|
421
714
|
};
|
|
@@ -427,37 +720,7 @@ var CodexCliLanguageModel = class {
|
|
|
427
720
|
}
|
|
428
721
|
options.abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
429
722
|
}
|
|
430
|
-
|
|
431
|
-
child.stdout.setEncoding("utf8");
|
|
432
|
-
child.stdout.on("data", (chunk) => {
|
|
433
|
-
const lines = chunk.split(/\r?\n/).filter(Boolean);
|
|
434
|
-
for (const line of lines) {
|
|
435
|
-
const evt = this.parseJsonLine(line);
|
|
436
|
-
if (!evt) continue;
|
|
437
|
-
const msg = evt.msg;
|
|
438
|
-
const type = msg?.type;
|
|
439
|
-
if (type === "session_configured" && msg) {
|
|
440
|
-
this.sessionId = msg.session_id;
|
|
441
|
-
controller.enqueue({
|
|
442
|
-
type: "response-metadata",
|
|
443
|
-
id: crypto.randomUUID(),
|
|
444
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
445
|
-
modelId: this.modelId
|
|
446
|
-
});
|
|
447
|
-
} else if (type === "task_complete" && msg) {
|
|
448
|
-
const last = msg.last_agent_message;
|
|
449
|
-
if (typeof last === "string") {
|
|
450
|
-
accumulatedText = last;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
});
|
|
455
|
-
child.on("error", (e) => {
|
|
456
|
-
if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
|
|
457
|
-
controller.error(this.handleSpawnError(e, promptExcerpt));
|
|
458
|
-
});
|
|
459
|
-
child.on("close", (code) => {
|
|
460
|
-
if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
|
|
723
|
+
const finishStream = (code) => {
|
|
461
724
|
if (code !== 0) {
|
|
462
725
|
controller.error(
|
|
463
726
|
createAPICallError({
|
|
@@ -469,6 +732,16 @@ var CodexCliLanguageModel = class {
|
|
|
469
732
|
);
|
|
470
733
|
return;
|
|
471
734
|
}
|
|
735
|
+
if (turnFailureMessage) {
|
|
736
|
+
controller.error(
|
|
737
|
+
createAPICallError({
|
|
738
|
+
message: turnFailureMessage,
|
|
739
|
+
stderr,
|
|
740
|
+
promptExcerpt
|
|
741
|
+
})
|
|
742
|
+
);
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
472
745
|
let finalText = accumulatedText;
|
|
473
746
|
if (!finalText && lastMessagePath) {
|
|
474
747
|
try {
|
|
@@ -482,17 +755,84 @@ var CodexCliLanguageModel = class {
|
|
|
482
755
|
}
|
|
483
756
|
}
|
|
484
757
|
if (finalText) {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
}
|
|
488
|
-
controller.enqueue({ type: "text-
|
|
758
|
+
const textId = crypto.randomUUID();
|
|
759
|
+
controller.enqueue({ type: "text-start", id: textId });
|
|
760
|
+
controller.enqueue({ type: "text-delta", id: textId, delta: finalText });
|
|
761
|
+
controller.enqueue({ type: "text-end", id: textId });
|
|
489
762
|
}
|
|
763
|
+
const usageSummary = lastUsage ?? { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
490
764
|
controller.enqueue({
|
|
491
765
|
type: "finish",
|
|
492
766
|
finishReason: "stop",
|
|
493
|
-
usage:
|
|
767
|
+
usage: usageSummary
|
|
494
768
|
});
|
|
495
769
|
controller.close();
|
|
770
|
+
};
|
|
771
|
+
child.stderr.on("data", (d) => stderr += String(d));
|
|
772
|
+
child.stdout.setEncoding("utf8");
|
|
773
|
+
child.stdout.on("data", (chunk) => {
|
|
774
|
+
const lines = chunk.split(/\r?\n/).filter(Boolean);
|
|
775
|
+
for (const line of lines) {
|
|
776
|
+
const event = this.parseExperimentalJsonEvent(line);
|
|
777
|
+
if (!event) continue;
|
|
778
|
+
if (event.type === "thread.started" && typeof event.thread_id === "string") {
|
|
779
|
+
this.sessionId = event.thread_id;
|
|
780
|
+
if (!responseMetadataSent) {
|
|
781
|
+
responseMetadataSent = true;
|
|
782
|
+
sendMetadata();
|
|
783
|
+
}
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
786
|
+
if (event.type === "session.created" && typeof event.session_id === "string") {
|
|
787
|
+
this.sessionId = event.session_id;
|
|
788
|
+
if (!responseMetadataSent) {
|
|
789
|
+
responseMetadataSent = true;
|
|
790
|
+
sendMetadata();
|
|
791
|
+
}
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
if (event.type === "turn.completed") {
|
|
795
|
+
const usageEvent = this.extractUsage(event);
|
|
796
|
+
if (usageEvent) {
|
|
797
|
+
lastUsage = usageEvent;
|
|
798
|
+
}
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
if (event.type === "turn.failed") {
|
|
802
|
+
const errorText = event.error && typeof event.error.message === "string" && event.error.message || (typeof event.message === "string" ? event.message : void 0);
|
|
803
|
+
turnFailureMessage = errorText ?? turnFailureMessage ?? "Codex turn failed";
|
|
804
|
+
sendMetadata({ error: turnFailureMessage });
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
if (event.type === "error") {
|
|
808
|
+
const errorText = typeof event.message === "string" ? event.message : void 0;
|
|
809
|
+
const effective = errorText ?? "Codex error";
|
|
810
|
+
turnFailureMessage = turnFailureMessage ?? effective;
|
|
811
|
+
sendMetadata({ error: effective });
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
if (event.type && event.type.startsWith("item.")) {
|
|
815
|
+
handleItemEvent(event);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
const cleanupSchema = () => {
|
|
820
|
+
if (!schemaPath) return;
|
|
821
|
+
try {
|
|
822
|
+
const schemaDir = path.dirname(schemaPath);
|
|
823
|
+
fs.rmSync(schemaDir, { recursive: true, force: true });
|
|
824
|
+
} catch {
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
child.on("error", (e) => {
|
|
828
|
+
if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
|
|
829
|
+
cleanupSchema();
|
|
830
|
+
controller.error(this.handleSpawnError(e, promptExcerpt));
|
|
831
|
+
});
|
|
832
|
+
child.on("close", (code) => {
|
|
833
|
+
if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
|
|
834
|
+
cleanupSchema();
|
|
835
|
+
setImmediate(() => finishStream(code));
|
|
496
836
|
});
|
|
497
837
|
},
|
|
498
838
|
cancel: () => {
|