ai-sdk-provider-codex-cli 0.6.0 → 1.0.0-beta.1

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
@@ -4,23 +4,34 @@
4
4
  [![npm downloads](https://img.shields.io/npm/dm/ai-sdk-provider-codex-cli.svg)](https://www.npmjs.com/package/ai-sdk-provider-codex-cli)
5
5
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
6
  ![Node >= 18](https://img.shields.io/badge/node-%3E%3D18-43853d?logo=node.js&logoColor=white)
7
- ![AI SDK v5](https://img.shields.io/badge/AI%20SDK-v5-000?logo=vercel&logoColor=white)
7
+ ![AI SDK v6 beta](https://img.shields.io/badge/AI%20SDK-v6%20beta-000?logo=vercel&logoColor=white)
8
8
  ![Modules: ESM + CJS](https://img.shields.io/badge/modules-ESM%20%2B%20CJS-3178c6)
9
9
  ![TypeScript](https://img.shields.io/badge/TypeScript-blue)
10
10
  [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/ben-vargas/ai-sdk-provider-codex-cli/issues)
11
11
  [![Latest Release](https://img.shields.io/github/v/release/ben-vargas/ai-sdk-provider-codex-cli?display_name=tag)](https://github.com/ben-vargas/ai-sdk-provider-codex-cli/releases/latest)
12
12
 
13
- A community provider for Vercel AI SDK v5 that uses OpenAIs Codex CLI (non‑interactive `codex exec`) to talk to GPT‑5.1 class models (`gpt-5.1`, the Codex-specific `gpt-5.1-codex`, the flagship `gpt-5.1-codex-max`, and the lightweight `gpt-5.1-codex-mini` slugs) with your ChatGPT Plus/Pro subscription. The provider spawns the Codex CLI process, parses its JSONL output, and adapts it to the AI SDK LanguageModelV2 interface. Legacy GPT-5 / GPT-5-codex slugs remain compatible for existing workflows.
13
+ A community provider for Vercel AI SDK v6 (beta) that uses OpenAI's Codex CLI (non‑interactive `codex exec`) to talk to GPT‑5.1 class models (`gpt-5.1`, the Codex-specific `gpt-5.1-codex`, the flagship `gpt-5.1-codex-max`, and the lightweight `gpt-5.1-codex-mini` slugs) with your ChatGPT Plus/Pro subscription. The provider spawns the Codex CLI process, parses its JSONL output, and adapts it to the AI SDK LanguageModelV3 interface. Legacy GPT-5 / GPT-5-codex slugs remain compatible for existing workflows.
14
+
15
+ > **⚠️ AI SDK v6 Beta**: This version (`1.0.0-beta.x`) requires AI SDK v6 beta packages. For AI SDK v5, use the `0.x` releases from the main branch.
14
16
 
15
17
  - Works with `generateText`, `streamText`, and `generateObject` (native JSON Schema support via `--output-schema`)
16
18
  - Uses ChatGPT OAuth from `codex login` (tokens in `~/.codex/auth.json`) or `OPENAI_API_KEY`
17
19
  - Node-only (spawns a local process); supports CI and local dev
20
+ - **v1.0.0-beta.1**: AI SDK v6 migration with LanguageModelV3 interface
18
21
  - **v0.5.0**: Adds comprehensive logging system with verbose mode and custom logger support
19
22
  - **v0.3.0**: Adds comprehensive tool streaming support for monitoring autonomous tool execution
20
- - **v0.2.0 Breaking Changes**: Switched to `--experimental-json` and native schema enforcement (see [CHANGELOG](CHANGELOG.md))
23
+
24
+ ## Version Compatibility
25
+
26
+ | Provider Version | AI SDK Version | NPM Tag | Status | Branch |
27
+ | ---------------- | -------------- | --------- | ------ | ------------ |
28
+ | 1.x.x-beta | v6 (beta) | `beta` | Beta | `ai-sdk-v6` |
29
+ | 0.x.x | v5 | `latest` | Stable | `main` |
21
30
 
22
31
  ## Installation
23
32
 
33
+ ### For AI SDK v6 (beta)
34
+
24
35
  1. Install and authenticate Codex CLI
25
36
 
26
37
  ```bash
@@ -28,18 +39,24 @@ npm i -g @openai/codex
28
39
  codex login # or set OPENAI_API_KEY
29
40
  ```
30
41
 
31
- > **⚠️ Version Requirement**: Requires Codex CLI **>= 0.42.0** for `--experimental-json` and `--output-schema` support. **>= 0.60.0 recommended** for `gpt-5.1-codex-max` and `xhigh` reasoning effort. If you supply your own Codex CLI (global install or custom `codexPath`/`allowNpx`), check it with `codex --version` and upgrade if needed. The optional dependency `@openai/codex` in this package pulls a compatible version automatically.
32
- >
33
- > ```bash
34
- > npm i -g @openai/codex@latest
35
- > ```
42
+ 2. Install provider and AI SDK v6 beta
43
+
44
+ ```bash
45
+ npm i ai@beta ai-sdk-provider-codex-cli@beta
46
+ ```
36
47
 
37
- 2. Install provider and AI SDK
48
+ ### For AI SDK v5 (stable)
38
49
 
39
50
  ```bash
40
51
  npm i ai ai-sdk-provider-codex-cli
41
52
  ```
42
53
 
54
+ > **⚠️ Codex CLI Version**: Requires Codex CLI **>= 0.42.0** for `--experimental-json` and `--output-schema` support. **>= 0.60.0 recommended** for `gpt-5.1-codex-max` and `xhigh` reasoning effort. If you supply your own Codex CLI (global install or custom `codexPath`/`allowNpx`), check it with `codex --version` and upgrade if needed. The optional dependency `@openai/codex` in this package pulls a compatible version automatically.
55
+ >
56
+ > ```bash
57
+ > npm i -g @openai/codex@latest
58
+ > ```
59
+
43
60
  ## Quick Start
44
61
 
45
62
  Text generation
@@ -95,7 +112,7 @@ console.log(object);
95
112
 
96
113
  ## Features
97
114
 
98
- - AI SDK v5 compatible (LanguageModelV2)
115
+ - AI SDK v6 compatible (LanguageModelV3)
99
116
  - Streaming and non‑streaming
100
117
  - **Configurable logging** (v0.5.0+) - Verbose mode, custom loggers, or silent operation
101
118
  - **Tool streaming support** (v0.3.0+) - Monitor autonomous tool execution in real-time
@@ -104,6 +121,48 @@ console.log(object);
104
121
  - Safe defaults for non‑interactive automation (`on-failure`, `workspace-write`, `--skip-git-repo-check`)
105
122
  - Fallback to `npx @openai/codex` when not on PATH (`allowNpx`)
106
123
  - Usage tracking from experimental JSON event format
124
+ - **Image support** - Pass images to vision-capable models via `--image` flag
125
+
126
+ ### Image Support
127
+
128
+ The provider supports multimodal (image) inputs for vision-capable models:
129
+
130
+ ```js
131
+ import { generateText } from 'ai';
132
+ import { codexCli } from 'ai-sdk-provider-codex-cli';
133
+ import { readFileSync } from 'fs';
134
+
135
+ const model = codexCli('gpt-5.1-codex', { allowNpx: true, skipGitRepoCheck: true });
136
+ const imageBuffer = readFileSync('./screenshot.png');
137
+
138
+ const { text } = await generateText({
139
+ model,
140
+ messages: [
141
+ {
142
+ role: 'user',
143
+ content: [
144
+ { type: 'text', text: 'What do you see in this image?' },
145
+ { type: 'image', image: imageBuffer, mimeType: 'image/png' },
146
+ ],
147
+ },
148
+ ],
149
+ });
150
+ console.log(text);
151
+ ```
152
+
153
+ **Supported image formats:**
154
+
155
+ - Base64 data URL (`data:image/png;base64,...`)
156
+ - Base64 string (without data URL prefix)
157
+ - `Buffer` / `Uint8Array` / `ArrayBuffer`
158
+
159
+ **Not supported:**
160
+
161
+ - HTTP/HTTPS URLs (images must be provided as binary data)
162
+
163
+ Images are written to temporary files and passed to Codex CLI via the `--image` flag. Temp files are automatically cleaned up after the request completes.
164
+
165
+ See [examples/image-support.mjs](examples/image-support.mjs) for a complete working example.
107
166
 
108
167
  ### Tool Streaming (v0.3.0+)
109
168
 
package/dist/index.cjs CHANGED
@@ -167,6 +167,94 @@ function validateModelId(modelId) {
167
167
  if (!modelId || modelId.trim() === "") return "Model ID cannot be empty";
168
168
  return void 0;
169
169
  }
170
+ function extractImageData(part) {
171
+ if (typeof part !== "object" || part === null) return null;
172
+ const p = part;
173
+ const mimeType = p.mimeType || "image/png";
174
+ if (typeof p.image === "string") {
175
+ return extractFromString(p.image, mimeType);
176
+ }
177
+ if (p.image instanceof URL) {
178
+ if (p.image.protocol === "data:") {
179
+ const dataUrlStr = p.image.toString();
180
+ const match = dataUrlStr.match(/^data:([^;,]+)/);
181
+ const extractedMimeType = match?.[1] || mimeType;
182
+ return { data: dataUrlStr, mimeType: extractedMimeType };
183
+ }
184
+ return null;
185
+ }
186
+ if (Buffer.isBuffer(p.image)) {
187
+ const base64 = p.image.toString("base64");
188
+ return { data: `data:${mimeType};base64,${base64}`, mimeType };
189
+ }
190
+ if (p.image instanceof ArrayBuffer || p.image instanceof Uint8Array) {
191
+ const buffer = Buffer.from(p.image);
192
+ const base64 = buffer.toString("base64");
193
+ return { data: `data:${mimeType};base64,${base64}`, mimeType };
194
+ }
195
+ if (typeof p.data === "string") {
196
+ return extractFromString(p.data, mimeType);
197
+ }
198
+ if (typeof p.url === "string") {
199
+ return extractFromString(p.url, mimeType);
200
+ }
201
+ return null;
202
+ }
203
+ function extractFromString(value, fallbackMimeType) {
204
+ const trimmed = value.trim();
205
+ if (/^https?:\/\//i.test(trimmed)) {
206
+ return null;
207
+ }
208
+ if (trimmed.startsWith("data:")) {
209
+ if (!trimmed.includes(";base64,")) {
210
+ return null;
211
+ }
212
+ const match = trimmed.match(/^data:([^;,]+)/);
213
+ const mimeType = match?.[1] || fallbackMimeType;
214
+ return { data: trimmed, mimeType };
215
+ }
216
+ return {
217
+ data: `data:${fallbackMimeType};base64,${trimmed}`,
218
+ mimeType: fallbackMimeType
219
+ };
220
+ }
221
+ function writeImageToTempFile(imageData) {
222
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "codex-img-"));
223
+ const ext = getExtensionFromMimeType(imageData.mimeType);
224
+ const filePath = path.join(dir, `image.${ext}`);
225
+ const base64Match = imageData.data.match(/^data:[^;]+;base64,(.+)$/);
226
+ if (!base64Match) {
227
+ throw new Error("Invalid data URL format: expected data:[type];base64,[data]");
228
+ }
229
+ const buffer = Buffer.from(base64Match[1], "base64");
230
+ fs.writeFileSync(filePath, buffer);
231
+ return filePath;
232
+ }
233
+ function cleanupTempImages(paths) {
234
+ for (const filePath of paths) {
235
+ try {
236
+ fs.rmSync(filePath, { force: true });
237
+ const dir = filePath.replace(/[/\\][^/\\]+$/, "");
238
+ if (dir.includes("codex-img-")) {
239
+ fs.rmSync(dir, { force: true, recursive: true });
240
+ }
241
+ } catch {
242
+ }
243
+ }
244
+ }
245
+ function getExtensionFromMimeType(mimeType) {
246
+ if (!mimeType) return "png";
247
+ const mapping = {
248
+ "image/png": "png",
249
+ "image/jpeg": "jpg",
250
+ "image/jpg": "jpg",
251
+ "image/gif": "gif",
252
+ "image/webp": "webp",
253
+ "image/bmp": "bmp",
254
+ "image/svg+xml": "svg"
255
+ };
256
+ return mapping[mimeType.toLowerCase()] || mimeType.split("/")[1] || "png";
257
+ }
170
258
 
171
259
  // src/message-mapper.ts
172
260
  function isTextPart(p) {
@@ -187,6 +275,7 @@ function isToolItem(p) {
187
275
  function mapMessagesToPrompt(prompt) {
188
276
  const warnings = [];
189
277
  const parts = [];
278
+ const images = [];
190
279
  let systemText;
191
280
  for (const msg of prompt) {
192
281
  if (msg.role === "system") {
@@ -199,8 +288,14 @@ function mapMessagesToPrompt(prompt) {
199
288
  } else if (Array.isArray(msg.content)) {
200
289
  const text = msg.content.filter(isTextPart).map((p) => p.text).join("\n");
201
290
  if (text) parts.push(`Human: ${text}`);
202
- const images = msg.content.filter(isImagePart);
203
- if (images.length) warnings.push("Image inputs ignored by Codex CLI integration.");
291
+ for (const part of msg.content.filter(isImagePart)) {
292
+ const imageData = extractImageData(part);
293
+ if (imageData) {
294
+ images.push(imageData);
295
+ } else {
296
+ warnings.push("Unsupported image format in message (HTTP URLs not supported)");
297
+ }
298
+ }
204
299
  }
205
300
  continue;
206
301
  }
@@ -227,7 +322,7 @@ function mapMessagesToPrompt(prompt) {
227
322
  let promptText = "";
228
323
  if (systemText) promptText += systemText + "\n\n";
229
324
  promptText += parts.join("\n\n");
230
- return { promptText, ...warnings.length ? { warnings } : {} };
325
+ return { promptText, images, ...warnings.length ? { warnings } : {} };
231
326
  }
232
327
  function createAPICallError({
233
328
  message,
@@ -293,7 +388,7 @@ function resolveCodexPath(explicitPath, allowNpx) {
293
388
  }
294
389
  }
295
390
  var CodexCliLanguageModel = class {
296
- specificationVersion = "v2";
391
+ specificationVersion = "v3";
297
392
  provider = "codex-cli";
298
393
  defaultObjectGenerationMode = "json";
299
394
  supportsImageUrls = false;
@@ -404,7 +499,7 @@ var CodexCliLanguageModel = class {
404
499
  const current = typeof data.type === "string" ? data.type : void 0;
405
500
  return legacy ?? current;
406
501
  }
407
- buildArgs(promptText, responseFormat, settings = this.settings) {
502
+ buildArgs(promptText, images = [], responseFormat, settings = this.settings) {
408
503
  const base = resolveCodexPath(settings.codexPath, settings.allowNpx);
409
504
  const args = [...base.args, "exec", "--experimental-json"];
410
505
  if (settings.fullAuto) {
@@ -479,6 +574,16 @@ var CodexCliLanguageModel = class {
479
574
  args.push("--output-schema", schemaPath);
480
575
  }
481
576
  }
577
+ const tempImagePaths = [];
578
+ for (const img of images) {
579
+ try {
580
+ const tempPath = writeImageToTempFile(img);
581
+ tempImagePaths.push(tempPath);
582
+ args.push("--image", tempPath);
583
+ } catch (e) {
584
+ this.logger.warn(`[codex-cli] Failed to write image to temp file: ${String(e)}`);
585
+ }
586
+ }
482
587
  args.push(promptText);
483
588
  const env = {
484
589
  ...process.env,
@@ -500,7 +605,8 @@ var CodexCliLanguageModel = class {
500
605
  cwd: settings.cwd,
501
606
  lastMessagePath,
502
607
  lastMessageIsTemp,
503
- schemaPath
608
+ schemaPath,
609
+ tempImagePaths: tempImagePaths.length > 0 ? tempImagePaths : void 0
504
610
  };
505
611
  }
506
612
  applyMcpSettings(args, settings) {
@@ -621,8 +727,8 @@ var CodexCliLanguageModel = class {
621
727
  const add = (setting, name) => {
622
728
  if (setting !== void 0)
623
729
  unsupported.push({
624
- type: "unsupported-setting",
625
- setting: name,
730
+ type: "unsupported",
731
+ feature: name,
626
732
  details: `Codex CLI does not support ${name}; it will be ignored.`
627
733
  });
628
734
  };
@@ -645,15 +751,21 @@ var CodexCliLanguageModel = class {
645
751
  extractUsage(evt) {
646
752
  const reported = evt.usage;
647
753
  if (!reported) return void 0;
648
- const inputTokens = reported.input_tokens ?? 0;
649
- const outputTokens = reported.output_tokens ?? 0;
754
+ const inputTotal = reported.input_tokens ?? 0;
755
+ const outputTotal = reported.output_tokens ?? 0;
650
756
  const cachedInputTokens = reported.cached_input_tokens ?? 0;
651
757
  return {
652
- inputTokens,
653
- outputTokens,
654
- // totalTokens should not double-count cached tokens; track cached separately
655
- totalTokens: inputTokens + outputTokens,
656
- cachedInputTokens
758
+ inputTokens: {
759
+ total: inputTotal,
760
+ noCache: inputTotal - cachedInputTokens,
761
+ cacheRead: cachedInputTokens,
762
+ cacheWrite: void 0
763
+ },
764
+ outputTokens: {
765
+ total: outputTotal,
766
+ text: outputTotal,
767
+ reasoning: void 0
768
+ }
657
769
  };
658
770
  }
659
771
  getToolName(item) {
@@ -830,14 +942,14 @@ var CodexCliLanguageModel = class {
830
942
  }
831
943
  async doGenerate(options) {
832
944
  this.logger.debug(`[codex-cli] Starting doGenerate request with model: ${this.modelId}`);
833
- const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
945
+ const { promptText, images, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
834
946
  const promptExcerpt = promptText.slice(0, 200);
835
947
  const warnings = [
836
948
  ...this.mapWarnings(options),
837
949
  ...mappingWarnings?.map((m) => ({ type: "other", message: m })) || []
838
950
  ];
839
951
  this.logger.debug(
840
- `[codex-cli] Converted ${options.prompt.length} messages, response format: ${options.responseFormat?.type ?? "none"}`
952
+ `[codex-cli] Converted ${options.prompt.length} messages (${images.length} images), response format: ${options.responseFormat?.type ?? "none"}`
841
953
  );
842
954
  const providerOptions = await providerUtils.parseProviderOptions({
843
955
  provider: this.provider,
@@ -846,16 +958,15 @@ var CodexCliLanguageModel = class {
846
958
  });
847
959
  const effectiveSettings = this.mergeSettings(providerOptions);
848
960
  const responseFormat = options.responseFormat?.type === "json" ? { type: "json", schema: options.responseFormat.schema } : void 0;
849
- const { cmd, args, env, cwd, lastMessagePath, lastMessageIsTemp, schemaPath } = this.buildArgs(
850
- promptText,
851
- responseFormat,
852
- effectiveSettings
853
- );
961
+ const { cmd, args, env, cwd, lastMessagePath, lastMessageIsTemp, schemaPath, tempImagePaths } = this.buildArgs(promptText, images, responseFormat, effectiveSettings);
854
962
  this.logger.debug(
855
963
  `[codex-cli] Executing Codex CLI: ${cmd} with ${args.length} arguments, cwd: ${cwd ?? "default"}`
856
964
  );
857
965
  let text = "";
858
- const usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
966
+ let usage = {
967
+ inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: void 0 },
968
+ outputTokens: { total: 0, text: 0, reasoning: void 0 }
969
+ };
859
970
  const finishReason = "stop";
860
971
  const startTime = Date.now();
861
972
  const child = child_process.spawn(cmd, args, { env, cwd, stdio: ["ignore", "pipe", "pipe"] });
@@ -863,6 +974,16 @@ var CodexCliLanguageModel = class {
863
974
  if (options.abortSignal) {
864
975
  if (options.abortSignal.aborted) {
865
976
  child.kill("SIGTERM");
977
+ if (schemaPath) {
978
+ try {
979
+ const schemaDir = path.dirname(schemaPath);
980
+ fs.rmSync(schemaDir, { recursive: true, force: true });
981
+ } catch {
982
+ }
983
+ }
984
+ if (tempImagePaths?.length) {
985
+ cleanupTempImages(tempImagePaths);
986
+ }
866
987
  throw options.abortSignal.reason ?? new Error("Request aborted");
867
988
  }
868
989
  onAbort = () => child.kill("SIGTERM");
@@ -891,9 +1012,7 @@ var CodexCliLanguageModel = class {
891
1012
  if (event.type === "turn.completed") {
892
1013
  const usageEvent = this.extractUsage(event);
893
1014
  if (usageEvent) {
894
- usage.inputTokens = usageEvent.inputTokens;
895
- usage.outputTokens = usageEvent.outputTokens;
896
- usage.totalTokens = usageEvent.totalTokens;
1015
+ usage = usageEvent;
897
1016
  }
898
1017
  }
899
1018
  if (event.type === "item.completed" && this.getItemType(event.item) === "assistant_message" && typeof event.item?.text === "string") {
@@ -928,11 +1047,12 @@ var CodexCliLanguageModel = class {
928
1047
  );
929
1048
  return;
930
1049
  }
1050
+ const totalTokens = (usage.inputTokens.total ?? 0) + (usage.outputTokens.total ?? 0);
931
1051
  this.logger.info(
932
- `[codex-cli] Request completed - Session: ${this.sessionId ?? "N/A"}, Duration: ${duration}ms, Tokens: ${usage.totalTokens}`
1052
+ `[codex-cli] Request completed - Session: ${this.sessionId ?? "N/A"}, Duration: ${duration}ms, Tokens: ${totalTokens}`
933
1053
  );
934
1054
  this.logger.debug(
935
- `[codex-cli] Token usage - Input: ${usage.inputTokens}, Output: ${usage.outputTokens}, Total: ${usage.totalTokens}`
1055
+ `[codex-cli] Token usage - Input: ${usage.inputTokens.total ?? 0}, Output: ${usage.outputTokens.total ?? 0}, Total: ${totalTokens}`
936
1056
  );
937
1057
  resolve();
938
1058
  } else {
@@ -957,6 +1077,9 @@ var CodexCliLanguageModel = class {
957
1077
  } catch {
958
1078
  }
959
1079
  }
1080
+ if (tempImagePaths?.length) {
1081
+ cleanupTempImages(tempImagePaths);
1082
+ }
960
1083
  }
961
1084
  if (!text && lastMessagePath) {
962
1085
  try {
@@ -988,14 +1111,14 @@ var CodexCliLanguageModel = class {
988
1111
  }
989
1112
  async doStream(options) {
990
1113
  this.logger.debug(`[codex-cli] Starting doStream request with model: ${this.modelId}`);
991
- const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
1114
+ const { promptText, images, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
992
1115
  const promptExcerpt = promptText.slice(0, 200);
993
1116
  const warnings = [
994
1117
  ...this.mapWarnings(options),
995
1118
  ...mappingWarnings?.map((m) => ({ type: "other", message: m })) || []
996
1119
  ];
997
1120
  this.logger.debug(
998
- `[codex-cli] Converted ${options.prompt.length} messages for streaming, response format: ${options.responseFormat?.type ?? "none"}`
1121
+ `[codex-cli] Converted ${options.prompt.length} messages (${images.length} images) for streaming, response format: ${options.responseFormat?.type ?? "none"}`
999
1122
  );
1000
1123
  const providerOptions = await providerUtils.parseProviderOptions({
1001
1124
  provider: this.provider,
@@ -1004,11 +1127,7 @@ var CodexCliLanguageModel = class {
1004
1127
  });
1005
1128
  const effectiveSettings = this.mergeSettings(providerOptions);
1006
1129
  const responseFormat = options.responseFormat?.type === "json" ? { type: "json", schema: options.responseFormat.schema } : void 0;
1007
- const { cmd, args, env, cwd, lastMessagePath, lastMessageIsTemp, schemaPath } = this.buildArgs(
1008
- promptText,
1009
- responseFormat,
1010
- effectiveSettings
1011
- );
1130
+ const { cmd, args, env, cwd, lastMessagePath, lastMessageIsTemp, schemaPath, tempImagePaths } = this.buildArgs(promptText, images, responseFormat, effectiveSettings);
1012
1131
  this.logger.debug(
1013
1132
  `[codex-cli] Executing Codex CLI for streaming: ${cmd} with ${args.length} arguments`
1014
1133
  );
@@ -1023,6 +1142,18 @@ var CodexCliLanguageModel = class {
1023
1142
  let responseMetadataSent = false;
1024
1143
  let lastUsage;
1025
1144
  let turnFailureMessage;
1145
+ const cleanupTempFiles = () => {
1146
+ if (schemaPath) {
1147
+ try {
1148
+ const schemaDir = path.dirname(schemaPath);
1149
+ fs.rmSync(schemaDir, { recursive: true, force: true });
1150
+ } catch {
1151
+ }
1152
+ }
1153
+ if (tempImagePaths?.length) {
1154
+ cleanupTempImages(tempImagePaths);
1155
+ }
1156
+ };
1026
1157
  const sendMetadata = (meta = {}) => {
1027
1158
  controller.enqueue({
1028
1159
  type: "response-metadata",
@@ -1096,6 +1227,7 @@ var CodexCliLanguageModel = class {
1096
1227
  if (options.abortSignal) {
1097
1228
  if (options.abortSignal.aborted) {
1098
1229
  child.kill("SIGTERM");
1230
+ cleanupTempFiles();
1099
1231
  controller.error(options.abortSignal.reason ?? new Error("Request aborted"));
1100
1232
  return;
1101
1233
  }
@@ -1148,12 +1280,16 @@ var CodexCliLanguageModel = class {
1148
1280
  controller.enqueue({ type: "text-delta", id: textId, delta: finalText });
1149
1281
  controller.enqueue({ type: "text-end", id: textId });
1150
1282
  }
1151
- const usageSummary = lastUsage ?? { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
1283
+ const usageSummary = lastUsage ?? {
1284
+ inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: void 0 },
1285
+ outputTokens: { total: 0, text: 0, reasoning: void 0 }
1286
+ };
1287
+ const totalTokens = (usageSummary.inputTokens.total ?? 0) + (usageSummary.outputTokens.total ?? 0);
1152
1288
  this.logger.info(
1153
- `[codex-cli] Stream completed - Session: ${this.sessionId ?? "N/A"}, Duration: ${duration}ms, Tokens: ${usageSummary.totalTokens}`
1289
+ `[codex-cli] Stream completed - Session: ${this.sessionId ?? "N/A"}, Duration: ${duration}ms, Tokens: ${totalTokens}`
1154
1290
  );
1155
1291
  this.logger.debug(
1156
- `[codex-cli] Token usage - Input: ${usageSummary.inputTokens}, Output: ${usageSummary.outputTokens}, Total: ${usageSummary.totalTokens}`
1292
+ `[codex-cli] Token usage - Input: ${usageSummary.inputTokens.total ?? 0}, Output: ${usageSummary.outputTokens.total ?? 0}, Total: ${totalTokens}`
1157
1293
  );
1158
1294
  controller.enqueue({
1159
1295
  type: "finish",
@@ -1215,23 +1351,15 @@ var CodexCliLanguageModel = class {
1215
1351
  }
1216
1352
  }
1217
1353
  });
1218
- const cleanupSchema = () => {
1219
- if (!schemaPath) return;
1220
- try {
1221
- const schemaDir = path.dirname(schemaPath);
1222
- fs.rmSync(schemaDir, { recursive: true, force: true });
1223
- } catch {
1224
- }
1225
- };
1226
1354
  child.on("error", (e) => {
1227
1355
  this.logger.error(`[codex-cli] Stream spawn error: ${String(e)}`);
1228
1356
  if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
1229
- cleanupSchema();
1357
+ cleanupTempFiles();
1230
1358
  controller.error(this.handleSpawnError(e, promptExcerpt));
1231
1359
  });
1232
1360
  child.on("close", (code) => {
1233
1361
  if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
1234
- cleanupSchema();
1362
+ cleanupTempFiles();
1235
1363
  setImmediate(() => finishStream(code));
1236
1364
  });
1237
1365
  },
@@ -1265,8 +1393,8 @@ function createCodexCli(options = {}) {
1265
1393
  };
1266
1394
  provider$1.languageModel = createModel;
1267
1395
  provider$1.chat = createModel;
1268
- provider$1.textEmbeddingModel = ((modelId) => {
1269
- throw new provider.NoSuchModelError({ modelId, modelType: "textEmbeddingModel" });
1396
+ provider$1.embeddingModel = ((modelId) => {
1397
+ throw new provider.NoSuchModelError({ modelId, modelType: "embeddingModel" });
1270
1398
  });
1271
1399
  provider$1.imageModel = ((modelId) => {
1272
1400
  throw new provider.NoSuchModelError({ modelId, modelType: "imageModel" });
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { ProviderV2, LanguageModelV2 } from '@ai-sdk/provider';
1
+ import { ProviderV3, LanguageModelV3 } from '@ai-sdk/provider';
2
2
 
3
3
  /**
4
4
  * Logger interface for custom logging.
@@ -251,11 +251,11 @@ interface CodexCliProviderOptions {
251
251
  rmcpClient?: boolean;
252
252
  }
253
253
 
254
- interface CodexCliProvider extends ProviderV2 {
255
- (modelId: string, settings?: CodexCliSettings): LanguageModelV2;
256
- languageModel(modelId: string, settings?: CodexCliSettings): LanguageModelV2;
257
- chat(modelId: string, settings?: CodexCliSettings): LanguageModelV2;
258
- textEmbeddingModel(modelId: string): never;
254
+ interface CodexCliProvider extends ProviderV3 {
255
+ (modelId: string, settings?: CodexCliSettings): LanguageModelV3;
256
+ languageModel(modelId: string, settings?: CodexCliSettings): LanguageModelV3;
257
+ chat(modelId: string, settings?: CodexCliSettings): LanguageModelV3;
258
+ embeddingModel(modelId: string): never;
259
259
  imageModel(modelId: string): never;
260
260
  }
261
261
  declare function createCodexCli(options?: CodexCliProviderSettings): CodexCliProvider;
@@ -265,8 +265,8 @@ interface CodexLanguageModelOptions {
265
265
  id: string;
266
266
  settings?: CodexCliSettings;
267
267
  }
268
- declare class CodexCliLanguageModel implements LanguageModelV2 {
269
- readonly specificationVersion: "v2";
268
+ declare class CodexCliLanguageModel implements LanguageModelV3 {
269
+ readonly specificationVersion: "v3";
270
270
  readonly provider = "codex-cli";
271
271
  readonly defaultObjectGenerationMode: "json";
272
272
  readonly supportsImageUrls = false;
@@ -301,8 +301,8 @@ declare class CodexCliLanguageModel implements LanguageModelV2 {
301
301
  private emitToolInvocation;
302
302
  private emitToolResult;
303
303
  private handleSpawnError;
304
- doGenerate(options: Parameters<LanguageModelV2['doGenerate']>[0]): Promise<Awaited<ReturnType<LanguageModelV2['doGenerate']>>>;
305
- doStream(options: Parameters<LanguageModelV2['doStream']>[0]): Promise<Awaited<ReturnType<LanguageModelV2['doStream']>>>;
304
+ doGenerate(options: Parameters<LanguageModelV3['doGenerate']>[0]): Promise<Awaited<ReturnType<LanguageModelV3['doGenerate']>>>;
305
+ doStream(options: Parameters<LanguageModelV3['doStream']>[0]): Promise<Awaited<ReturnType<LanguageModelV3['doStream']>>>;
306
306
  }
307
307
 
308
308
  declare function isAuthenticationError(err: unknown): boolean;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ProviderV2, LanguageModelV2 } from '@ai-sdk/provider';
1
+ import { ProviderV3, LanguageModelV3 } from '@ai-sdk/provider';
2
2
 
3
3
  /**
4
4
  * Logger interface for custom logging.
@@ -251,11 +251,11 @@ interface CodexCliProviderOptions {
251
251
  rmcpClient?: boolean;
252
252
  }
253
253
 
254
- interface CodexCliProvider extends ProviderV2 {
255
- (modelId: string, settings?: CodexCliSettings): LanguageModelV2;
256
- languageModel(modelId: string, settings?: CodexCliSettings): LanguageModelV2;
257
- chat(modelId: string, settings?: CodexCliSettings): LanguageModelV2;
258
- textEmbeddingModel(modelId: string): never;
254
+ interface CodexCliProvider extends ProviderV3 {
255
+ (modelId: string, settings?: CodexCliSettings): LanguageModelV3;
256
+ languageModel(modelId: string, settings?: CodexCliSettings): LanguageModelV3;
257
+ chat(modelId: string, settings?: CodexCliSettings): LanguageModelV3;
258
+ embeddingModel(modelId: string): never;
259
259
  imageModel(modelId: string): never;
260
260
  }
261
261
  declare function createCodexCli(options?: CodexCliProviderSettings): CodexCliProvider;
@@ -265,8 +265,8 @@ interface CodexLanguageModelOptions {
265
265
  id: string;
266
266
  settings?: CodexCliSettings;
267
267
  }
268
- declare class CodexCliLanguageModel implements LanguageModelV2 {
269
- readonly specificationVersion: "v2";
268
+ declare class CodexCliLanguageModel implements LanguageModelV3 {
269
+ readonly specificationVersion: "v3";
270
270
  readonly provider = "codex-cli";
271
271
  readonly defaultObjectGenerationMode: "json";
272
272
  readonly supportsImageUrls = false;
@@ -301,8 +301,8 @@ declare class CodexCliLanguageModel implements LanguageModelV2 {
301
301
  private emitToolInvocation;
302
302
  private emitToolResult;
303
303
  private handleSpawnError;
304
- doGenerate(options: Parameters<LanguageModelV2['doGenerate']>[0]): Promise<Awaited<ReturnType<LanguageModelV2['doGenerate']>>>;
305
- doStream(options: Parameters<LanguageModelV2['doStream']>[0]): Promise<Awaited<ReturnType<LanguageModelV2['doStream']>>>;
304
+ doGenerate(options: Parameters<LanguageModelV3['doGenerate']>[0]): Promise<Awaited<ReturnType<LanguageModelV3['doGenerate']>>>;
305
+ doStream(options: Parameters<LanguageModelV3['doStream']>[0]): Promise<Awaited<ReturnType<LanguageModelV3['doStream']>>>;
306
306
  }
307
307
 
308
308
  declare function isAuthenticationError(err: unknown): boolean;
package/dist/index.js CHANGED
@@ -164,6 +164,94 @@ function validateModelId(modelId) {
164
164
  if (!modelId || modelId.trim() === "") return "Model ID cannot be empty";
165
165
  return void 0;
166
166
  }
167
+ function extractImageData(part) {
168
+ if (typeof part !== "object" || part === null) return null;
169
+ const p = part;
170
+ const mimeType = p.mimeType || "image/png";
171
+ if (typeof p.image === "string") {
172
+ return extractFromString(p.image, mimeType);
173
+ }
174
+ if (p.image instanceof URL) {
175
+ if (p.image.protocol === "data:") {
176
+ const dataUrlStr = p.image.toString();
177
+ const match = dataUrlStr.match(/^data:([^;,]+)/);
178
+ const extractedMimeType = match?.[1] || mimeType;
179
+ return { data: dataUrlStr, mimeType: extractedMimeType };
180
+ }
181
+ return null;
182
+ }
183
+ if (Buffer.isBuffer(p.image)) {
184
+ const base64 = p.image.toString("base64");
185
+ return { data: `data:${mimeType};base64,${base64}`, mimeType };
186
+ }
187
+ if (p.image instanceof ArrayBuffer || p.image instanceof Uint8Array) {
188
+ const buffer = Buffer.from(p.image);
189
+ const base64 = buffer.toString("base64");
190
+ return { data: `data:${mimeType};base64,${base64}`, mimeType };
191
+ }
192
+ if (typeof p.data === "string") {
193
+ return extractFromString(p.data, mimeType);
194
+ }
195
+ if (typeof p.url === "string") {
196
+ return extractFromString(p.url, mimeType);
197
+ }
198
+ return null;
199
+ }
200
+ function extractFromString(value, fallbackMimeType) {
201
+ const trimmed = value.trim();
202
+ if (/^https?:\/\//i.test(trimmed)) {
203
+ return null;
204
+ }
205
+ if (trimmed.startsWith("data:")) {
206
+ if (!trimmed.includes(";base64,")) {
207
+ return null;
208
+ }
209
+ const match = trimmed.match(/^data:([^;,]+)/);
210
+ const mimeType = match?.[1] || fallbackMimeType;
211
+ return { data: trimmed, mimeType };
212
+ }
213
+ return {
214
+ data: `data:${fallbackMimeType};base64,${trimmed}`,
215
+ mimeType: fallbackMimeType
216
+ };
217
+ }
218
+ function writeImageToTempFile(imageData) {
219
+ const dir = mkdtempSync(join(tmpdir(), "codex-img-"));
220
+ const ext = getExtensionFromMimeType(imageData.mimeType);
221
+ const filePath = join(dir, `image.${ext}`);
222
+ const base64Match = imageData.data.match(/^data:[^;]+;base64,(.+)$/);
223
+ if (!base64Match) {
224
+ throw new Error("Invalid data URL format: expected data:[type];base64,[data]");
225
+ }
226
+ const buffer = Buffer.from(base64Match[1], "base64");
227
+ writeFileSync(filePath, buffer);
228
+ return filePath;
229
+ }
230
+ function cleanupTempImages(paths) {
231
+ for (const filePath of paths) {
232
+ try {
233
+ rmSync(filePath, { force: true });
234
+ const dir = filePath.replace(/[/\\][^/\\]+$/, "");
235
+ if (dir.includes("codex-img-")) {
236
+ rmSync(dir, { force: true, recursive: true });
237
+ }
238
+ } catch {
239
+ }
240
+ }
241
+ }
242
+ function getExtensionFromMimeType(mimeType) {
243
+ if (!mimeType) return "png";
244
+ const mapping = {
245
+ "image/png": "png",
246
+ "image/jpeg": "jpg",
247
+ "image/jpg": "jpg",
248
+ "image/gif": "gif",
249
+ "image/webp": "webp",
250
+ "image/bmp": "bmp",
251
+ "image/svg+xml": "svg"
252
+ };
253
+ return mapping[mimeType.toLowerCase()] || mimeType.split("/")[1] || "png";
254
+ }
167
255
 
168
256
  // src/message-mapper.ts
169
257
  function isTextPart(p) {
@@ -184,6 +272,7 @@ function isToolItem(p) {
184
272
  function mapMessagesToPrompt(prompt) {
185
273
  const warnings = [];
186
274
  const parts = [];
275
+ const images = [];
187
276
  let systemText;
188
277
  for (const msg of prompt) {
189
278
  if (msg.role === "system") {
@@ -196,8 +285,14 @@ function mapMessagesToPrompt(prompt) {
196
285
  } else if (Array.isArray(msg.content)) {
197
286
  const text = msg.content.filter(isTextPart).map((p) => p.text).join("\n");
198
287
  if (text) parts.push(`Human: ${text}`);
199
- const images = msg.content.filter(isImagePart);
200
- if (images.length) warnings.push("Image inputs ignored by Codex CLI integration.");
288
+ for (const part of msg.content.filter(isImagePart)) {
289
+ const imageData = extractImageData(part);
290
+ if (imageData) {
291
+ images.push(imageData);
292
+ } else {
293
+ warnings.push("Unsupported image format in message (HTTP URLs not supported)");
294
+ }
295
+ }
201
296
  }
202
297
  continue;
203
298
  }
@@ -224,7 +319,7 @@ function mapMessagesToPrompt(prompt) {
224
319
  let promptText = "";
225
320
  if (systemText) promptText += systemText + "\n\n";
226
321
  promptText += parts.join("\n\n");
227
- return { promptText, ...warnings.length ? { warnings } : {} };
322
+ return { promptText, images, ...warnings.length ? { warnings } : {} };
228
323
  }
229
324
  function createAPICallError({
230
325
  message,
@@ -290,7 +385,7 @@ function resolveCodexPath(explicitPath, allowNpx) {
290
385
  }
291
386
  }
292
387
  var CodexCliLanguageModel = class {
293
- specificationVersion = "v2";
388
+ specificationVersion = "v3";
294
389
  provider = "codex-cli";
295
390
  defaultObjectGenerationMode = "json";
296
391
  supportsImageUrls = false;
@@ -401,7 +496,7 @@ var CodexCliLanguageModel = class {
401
496
  const current = typeof data.type === "string" ? data.type : void 0;
402
497
  return legacy ?? current;
403
498
  }
404
- buildArgs(promptText, responseFormat, settings = this.settings) {
499
+ buildArgs(promptText, images = [], responseFormat, settings = this.settings) {
405
500
  const base = resolveCodexPath(settings.codexPath, settings.allowNpx);
406
501
  const args = [...base.args, "exec", "--experimental-json"];
407
502
  if (settings.fullAuto) {
@@ -476,6 +571,16 @@ var CodexCliLanguageModel = class {
476
571
  args.push("--output-schema", schemaPath);
477
572
  }
478
573
  }
574
+ const tempImagePaths = [];
575
+ for (const img of images) {
576
+ try {
577
+ const tempPath = writeImageToTempFile(img);
578
+ tempImagePaths.push(tempPath);
579
+ args.push("--image", tempPath);
580
+ } catch (e) {
581
+ this.logger.warn(`[codex-cli] Failed to write image to temp file: ${String(e)}`);
582
+ }
583
+ }
479
584
  args.push(promptText);
480
585
  const env = {
481
586
  ...process.env,
@@ -497,7 +602,8 @@ var CodexCliLanguageModel = class {
497
602
  cwd: settings.cwd,
498
603
  lastMessagePath,
499
604
  lastMessageIsTemp,
500
- schemaPath
605
+ schemaPath,
606
+ tempImagePaths: tempImagePaths.length > 0 ? tempImagePaths : void 0
501
607
  };
502
608
  }
503
609
  applyMcpSettings(args, settings) {
@@ -618,8 +724,8 @@ var CodexCliLanguageModel = class {
618
724
  const add = (setting, name) => {
619
725
  if (setting !== void 0)
620
726
  unsupported.push({
621
- type: "unsupported-setting",
622
- setting: name,
727
+ type: "unsupported",
728
+ feature: name,
623
729
  details: `Codex CLI does not support ${name}; it will be ignored.`
624
730
  });
625
731
  };
@@ -642,15 +748,21 @@ var CodexCliLanguageModel = class {
642
748
  extractUsage(evt) {
643
749
  const reported = evt.usage;
644
750
  if (!reported) return void 0;
645
- const inputTokens = reported.input_tokens ?? 0;
646
- const outputTokens = reported.output_tokens ?? 0;
751
+ const inputTotal = reported.input_tokens ?? 0;
752
+ const outputTotal = reported.output_tokens ?? 0;
647
753
  const cachedInputTokens = reported.cached_input_tokens ?? 0;
648
754
  return {
649
- inputTokens,
650
- outputTokens,
651
- // totalTokens should not double-count cached tokens; track cached separately
652
- totalTokens: inputTokens + outputTokens,
653
- cachedInputTokens
755
+ inputTokens: {
756
+ total: inputTotal,
757
+ noCache: inputTotal - cachedInputTokens,
758
+ cacheRead: cachedInputTokens,
759
+ cacheWrite: void 0
760
+ },
761
+ outputTokens: {
762
+ total: outputTotal,
763
+ text: outputTotal,
764
+ reasoning: void 0
765
+ }
654
766
  };
655
767
  }
656
768
  getToolName(item) {
@@ -827,14 +939,14 @@ var CodexCliLanguageModel = class {
827
939
  }
828
940
  async doGenerate(options) {
829
941
  this.logger.debug(`[codex-cli] Starting doGenerate request with model: ${this.modelId}`);
830
- const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
942
+ const { promptText, images, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
831
943
  const promptExcerpt = promptText.slice(0, 200);
832
944
  const warnings = [
833
945
  ...this.mapWarnings(options),
834
946
  ...mappingWarnings?.map((m) => ({ type: "other", message: m })) || []
835
947
  ];
836
948
  this.logger.debug(
837
- `[codex-cli] Converted ${options.prompt.length} messages, response format: ${options.responseFormat?.type ?? "none"}`
949
+ `[codex-cli] Converted ${options.prompt.length} messages (${images.length} images), response format: ${options.responseFormat?.type ?? "none"}`
838
950
  );
839
951
  const providerOptions = await parseProviderOptions({
840
952
  provider: this.provider,
@@ -843,16 +955,15 @@ var CodexCliLanguageModel = class {
843
955
  });
844
956
  const effectiveSettings = this.mergeSettings(providerOptions);
845
957
  const responseFormat = options.responseFormat?.type === "json" ? { type: "json", schema: options.responseFormat.schema } : void 0;
846
- const { cmd, args, env, cwd, lastMessagePath, lastMessageIsTemp, schemaPath } = this.buildArgs(
847
- promptText,
848
- responseFormat,
849
- effectiveSettings
850
- );
958
+ const { cmd, args, env, cwd, lastMessagePath, lastMessageIsTemp, schemaPath, tempImagePaths } = this.buildArgs(promptText, images, responseFormat, effectiveSettings);
851
959
  this.logger.debug(
852
960
  `[codex-cli] Executing Codex CLI: ${cmd} with ${args.length} arguments, cwd: ${cwd ?? "default"}`
853
961
  );
854
962
  let text = "";
855
- const usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
963
+ let usage = {
964
+ inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: void 0 },
965
+ outputTokens: { total: 0, text: 0, reasoning: void 0 }
966
+ };
856
967
  const finishReason = "stop";
857
968
  const startTime = Date.now();
858
969
  const child = spawn(cmd, args, { env, cwd, stdio: ["ignore", "pipe", "pipe"] });
@@ -860,6 +971,16 @@ var CodexCliLanguageModel = class {
860
971
  if (options.abortSignal) {
861
972
  if (options.abortSignal.aborted) {
862
973
  child.kill("SIGTERM");
974
+ if (schemaPath) {
975
+ try {
976
+ const schemaDir = dirname(schemaPath);
977
+ rmSync(schemaDir, { recursive: true, force: true });
978
+ } catch {
979
+ }
980
+ }
981
+ if (tempImagePaths?.length) {
982
+ cleanupTempImages(tempImagePaths);
983
+ }
863
984
  throw options.abortSignal.reason ?? new Error("Request aborted");
864
985
  }
865
986
  onAbort = () => child.kill("SIGTERM");
@@ -888,9 +1009,7 @@ var CodexCliLanguageModel = class {
888
1009
  if (event.type === "turn.completed") {
889
1010
  const usageEvent = this.extractUsage(event);
890
1011
  if (usageEvent) {
891
- usage.inputTokens = usageEvent.inputTokens;
892
- usage.outputTokens = usageEvent.outputTokens;
893
- usage.totalTokens = usageEvent.totalTokens;
1012
+ usage = usageEvent;
894
1013
  }
895
1014
  }
896
1015
  if (event.type === "item.completed" && this.getItemType(event.item) === "assistant_message" && typeof event.item?.text === "string") {
@@ -925,11 +1044,12 @@ var CodexCliLanguageModel = class {
925
1044
  );
926
1045
  return;
927
1046
  }
1047
+ const totalTokens = (usage.inputTokens.total ?? 0) + (usage.outputTokens.total ?? 0);
928
1048
  this.logger.info(
929
- `[codex-cli] Request completed - Session: ${this.sessionId ?? "N/A"}, Duration: ${duration}ms, Tokens: ${usage.totalTokens}`
1049
+ `[codex-cli] Request completed - Session: ${this.sessionId ?? "N/A"}, Duration: ${duration}ms, Tokens: ${totalTokens}`
930
1050
  );
931
1051
  this.logger.debug(
932
- `[codex-cli] Token usage - Input: ${usage.inputTokens}, Output: ${usage.outputTokens}, Total: ${usage.totalTokens}`
1052
+ `[codex-cli] Token usage - Input: ${usage.inputTokens.total ?? 0}, Output: ${usage.outputTokens.total ?? 0}, Total: ${totalTokens}`
933
1053
  );
934
1054
  resolve();
935
1055
  } else {
@@ -954,6 +1074,9 @@ var CodexCliLanguageModel = class {
954
1074
  } catch {
955
1075
  }
956
1076
  }
1077
+ if (tempImagePaths?.length) {
1078
+ cleanupTempImages(tempImagePaths);
1079
+ }
957
1080
  }
958
1081
  if (!text && lastMessagePath) {
959
1082
  try {
@@ -985,14 +1108,14 @@ var CodexCliLanguageModel = class {
985
1108
  }
986
1109
  async doStream(options) {
987
1110
  this.logger.debug(`[codex-cli] Starting doStream request with model: ${this.modelId}`);
988
- const { promptText, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
1111
+ const { promptText, images, warnings: mappingWarnings } = mapMessagesToPrompt(options.prompt);
989
1112
  const promptExcerpt = promptText.slice(0, 200);
990
1113
  const warnings = [
991
1114
  ...this.mapWarnings(options),
992
1115
  ...mappingWarnings?.map((m) => ({ type: "other", message: m })) || []
993
1116
  ];
994
1117
  this.logger.debug(
995
- `[codex-cli] Converted ${options.prompt.length} messages for streaming, response format: ${options.responseFormat?.type ?? "none"}`
1118
+ `[codex-cli] Converted ${options.prompt.length} messages (${images.length} images) for streaming, response format: ${options.responseFormat?.type ?? "none"}`
996
1119
  );
997
1120
  const providerOptions = await parseProviderOptions({
998
1121
  provider: this.provider,
@@ -1001,11 +1124,7 @@ var CodexCliLanguageModel = class {
1001
1124
  });
1002
1125
  const effectiveSettings = this.mergeSettings(providerOptions);
1003
1126
  const responseFormat = options.responseFormat?.type === "json" ? { type: "json", schema: options.responseFormat.schema } : void 0;
1004
- const { cmd, args, env, cwd, lastMessagePath, lastMessageIsTemp, schemaPath } = this.buildArgs(
1005
- promptText,
1006
- responseFormat,
1007
- effectiveSettings
1008
- );
1127
+ const { cmd, args, env, cwd, lastMessagePath, lastMessageIsTemp, schemaPath, tempImagePaths } = this.buildArgs(promptText, images, responseFormat, effectiveSettings);
1009
1128
  this.logger.debug(
1010
1129
  `[codex-cli] Executing Codex CLI for streaming: ${cmd} with ${args.length} arguments`
1011
1130
  );
@@ -1020,6 +1139,18 @@ var CodexCliLanguageModel = class {
1020
1139
  let responseMetadataSent = false;
1021
1140
  let lastUsage;
1022
1141
  let turnFailureMessage;
1142
+ const cleanupTempFiles = () => {
1143
+ if (schemaPath) {
1144
+ try {
1145
+ const schemaDir = dirname(schemaPath);
1146
+ rmSync(schemaDir, { recursive: true, force: true });
1147
+ } catch {
1148
+ }
1149
+ }
1150
+ if (tempImagePaths?.length) {
1151
+ cleanupTempImages(tempImagePaths);
1152
+ }
1153
+ };
1023
1154
  const sendMetadata = (meta = {}) => {
1024
1155
  controller.enqueue({
1025
1156
  type: "response-metadata",
@@ -1093,6 +1224,7 @@ var CodexCliLanguageModel = class {
1093
1224
  if (options.abortSignal) {
1094
1225
  if (options.abortSignal.aborted) {
1095
1226
  child.kill("SIGTERM");
1227
+ cleanupTempFiles();
1096
1228
  controller.error(options.abortSignal.reason ?? new Error("Request aborted"));
1097
1229
  return;
1098
1230
  }
@@ -1145,12 +1277,16 @@ var CodexCliLanguageModel = class {
1145
1277
  controller.enqueue({ type: "text-delta", id: textId, delta: finalText });
1146
1278
  controller.enqueue({ type: "text-end", id: textId });
1147
1279
  }
1148
- const usageSummary = lastUsage ?? { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
1280
+ const usageSummary = lastUsage ?? {
1281
+ inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: void 0 },
1282
+ outputTokens: { total: 0, text: 0, reasoning: void 0 }
1283
+ };
1284
+ const totalTokens = (usageSummary.inputTokens.total ?? 0) + (usageSummary.outputTokens.total ?? 0);
1149
1285
  this.logger.info(
1150
- `[codex-cli] Stream completed - Session: ${this.sessionId ?? "N/A"}, Duration: ${duration}ms, Tokens: ${usageSummary.totalTokens}`
1286
+ `[codex-cli] Stream completed - Session: ${this.sessionId ?? "N/A"}, Duration: ${duration}ms, Tokens: ${totalTokens}`
1151
1287
  );
1152
1288
  this.logger.debug(
1153
- `[codex-cli] Token usage - Input: ${usageSummary.inputTokens}, Output: ${usageSummary.outputTokens}, Total: ${usageSummary.totalTokens}`
1289
+ `[codex-cli] Token usage - Input: ${usageSummary.inputTokens.total ?? 0}, Output: ${usageSummary.outputTokens.total ?? 0}, Total: ${totalTokens}`
1154
1290
  );
1155
1291
  controller.enqueue({
1156
1292
  type: "finish",
@@ -1212,23 +1348,15 @@ var CodexCliLanguageModel = class {
1212
1348
  }
1213
1349
  }
1214
1350
  });
1215
- const cleanupSchema = () => {
1216
- if (!schemaPath) return;
1217
- try {
1218
- const schemaDir = dirname(schemaPath);
1219
- rmSync(schemaDir, { recursive: true, force: true });
1220
- } catch {
1221
- }
1222
- };
1223
1351
  child.on("error", (e) => {
1224
1352
  this.logger.error(`[codex-cli] Stream spawn error: ${String(e)}`);
1225
1353
  if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
1226
- cleanupSchema();
1354
+ cleanupTempFiles();
1227
1355
  controller.error(this.handleSpawnError(e, promptExcerpt));
1228
1356
  });
1229
1357
  child.on("close", (code) => {
1230
1358
  if (options.abortSignal) options.abortSignal.removeEventListener("abort", onAbort);
1231
- cleanupSchema();
1359
+ cleanupTempFiles();
1232
1360
  setImmediate(() => finishStream(code));
1233
1361
  });
1234
1362
  },
@@ -1262,8 +1390,8 @@ function createCodexCli(options = {}) {
1262
1390
  };
1263
1391
  provider.languageModel = createModel;
1264
1392
  provider.chat = createModel;
1265
- provider.textEmbeddingModel = ((modelId) => {
1266
- throw new NoSuchModelError({ modelId, modelType: "textEmbeddingModel" });
1393
+ provider.embeddingModel = ((modelId) => {
1394
+ throw new NoSuchModelError({ modelId, modelType: "embeddingModel" });
1267
1395
  });
1268
1396
  provider.imageModel = ((modelId) => {
1269
1397
  throw new NoSuchModelError({ modelId, modelType: "imageModel" });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ai-sdk-provider-codex-cli",
3
- "version": "0.6.0",
4
- "description": "AI SDK v5 provider for OpenAI Codex CLI with native JSON Schema support",
3
+ "version": "1.0.0-beta.1",
4
+ "description": "AI SDK v6 provider for OpenAI Codex CLI with native JSON Schema support",
5
5
  "keywords": [
6
6
  "ai-sdk",
7
7
  "codex",
@@ -59,8 +59,8 @@
59
59
  "test:coverage": "vitest run --coverage"
60
60
  },
61
61
  "dependencies": {
62
- "@ai-sdk/provider": "2.0.0",
63
- "@ai-sdk/provider-utils": "3.0.3",
62
+ "@ai-sdk/provider": "3.0.0-beta.26",
63
+ "@ai-sdk/provider-utils": "4.0.0-beta.51",
64
64
  "jsonc-parser": "^3.3.1"
65
65
  },
66
66
  "optionalDependencies": {
@@ -70,7 +70,7 @@
70
70
  "@eslint/js": "^9.14.0",
71
71
  "@types/node": "20.19.6",
72
72
  "@vitest/coverage-v8": "^3.2.4",
73
- "ai": "5.0.93",
73
+ "ai": "6.0.0-beta.156",
74
74
  "eslint": "^9.14.0",
75
75
  "prettier": "^3.3.3",
76
76
  "tsup": "8.5.0",