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