@tanstack/openai-base 0.10.15 → 0.11.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.
@@ -0,0 +1,241 @@
1
+ //#region src/adapters/responses-user-tools.ts
2
+ function isRecord(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
5
+ function stringList(value) {
6
+ if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string")) return null;
7
+ return value;
8
+ }
9
+ function stringEnv(value) {
10
+ if (!isRecord(value)) return {};
11
+ const env = {};
12
+ for (const [key, entry] of Object.entries(value)) if (typeof entry === "string") env[key] = entry;
13
+ return env;
14
+ }
15
+ function nullableNumber(value) {
16
+ return typeof value === "number" || value === null ? value : null;
17
+ }
18
+ /**
19
+ * Hosted shell calls already include a `shell_call_output` in the same
20
+ * response. Those call ids must not pause the app for another run.
21
+ */
22
+ function hostedShellCallIds(output) {
23
+ const ids = /* @__PURE__ */ new Set();
24
+ for (const item of output) if (isRecord(item) && item.type === "shell_call_output" && typeof item.call_id === "string") ids.add(item.call_id);
25
+ return ids;
26
+ }
27
+ function readUserToolName(metadata) {
28
+ if (!isRecord(metadata)) return null;
29
+ const name = metadata.openaiUserTool;
30
+ if (name === "shell" || name === "apply_patch" || name === "local_shell") return name;
31
+ return null;
32
+ }
33
+ function readItemId(metadata) {
34
+ if (!isRecord(metadata)) return void 0;
35
+ return typeof metadata.itemId === "string" && metadata.itemId.length > 0 ? metadata.itemId : void 0;
36
+ }
37
+ function readMaxOutputLength(metadata) {
38
+ if (!isRecord(metadata)) return void 0;
39
+ const value = metadata.maxOutputLength;
40
+ return typeof value === "number" || value === null ? value : void 0;
41
+ }
42
+ function applyPatchOperation(value) {
43
+ if (!isRecord(value) || typeof value.path !== "string") return null;
44
+ if (value.type === "delete_file") return {
45
+ type: "delete_file",
46
+ path: value.path
47
+ };
48
+ if ((value.type === "create_file" || value.type === "update_file") && typeof value.diff === "string") return {
49
+ type: value.type,
50
+ path: value.path,
51
+ diff: value.diff
52
+ };
53
+ return null;
54
+ }
55
+ /**
56
+ * Read a user-run Responses output item.
57
+ * `bareShell` is true only once the full response is known. A shell call
58
+ * with no environment waits for that pass, so a hosted call that later
59
+ * carries `shell_call_output` is not asked of the app.
60
+ */
61
+ function readUserExecutedCall(item, options) {
62
+ if (!isRecord(item) || typeof item.type !== "string") return null;
63
+ const callId = typeof item.call_id === "string" ? item.call_id : "";
64
+ const itemId = typeof item.id === "string" ? item.id : void 0;
65
+ if (item.type === "apply_patch_call") {
66
+ const operation = applyPatchOperation(item.operation);
67
+ if (!callId || !operation) return null;
68
+ return {
69
+ name: "apply_patch",
70
+ callId,
71
+ ...itemId ? { itemId } : {},
72
+ input: { operation }
73
+ };
74
+ }
75
+ if (item.type === "local_shell_call") {
76
+ if (!isRecord(item.action)) return null;
77
+ const command = stringList(item.action.command);
78
+ if (!callId || !command || command.length === 0) return null;
79
+ return {
80
+ name: "local_shell",
81
+ callId,
82
+ ...itemId ? { itemId } : {},
83
+ input: item.action
84
+ };
85
+ }
86
+ if (item.type !== "shell_call") return null;
87
+ const environment = item.environment;
88
+ if (isRecord(environment)) {
89
+ if (environment.type !== "local") return null;
90
+ } else if (!options.bareShell) return null;
91
+ if (!callId || !isRecord(item.action)) return null;
92
+ const commands = stringList(item.action.commands);
93
+ if (!commands || commands.length === 0) return null;
94
+ const maxOutputLength = nullableNumber(item.action.max_output_length);
95
+ return {
96
+ name: "shell",
97
+ callId,
98
+ ...itemId ? { itemId } : {},
99
+ input: {
100
+ commands,
101
+ max_output_length: maxOutputLength,
102
+ timeout_ms: nullableNumber(item.action.timeout_ms)
103
+ },
104
+ maxOutputLength
105
+ };
106
+ }
107
+ function parseArguments(argumentsString) {
108
+ try {
109
+ const parsed = JSON.parse(argumentsString);
110
+ return isRecord(parsed) ? parsed : {};
111
+ } catch {
112
+ return {};
113
+ }
114
+ }
115
+ function parseContent(content) {
116
+ if (typeof content !== "string") return content;
117
+ try {
118
+ return JSON.parse(content);
119
+ } catch {
120
+ return content;
121
+ }
122
+ }
123
+ /** Replay a user-run tool call as the Responses input item OpenAI expects. */
124
+ function userToolRequestItem(toolCall) {
125
+ const name = readUserToolName(toolCall.metadata);
126
+ if (!name) return null;
127
+ const args = parseArguments(toolCall.function.arguments);
128
+ const itemId = readItemId(toolCall.metadata);
129
+ if (name === "apply_patch") {
130
+ const operation = applyPatchOperation(args.operation);
131
+ if (!operation) return null;
132
+ return {
133
+ type: "apply_patch_call",
134
+ call_id: toolCall.id,
135
+ status: "completed",
136
+ operation,
137
+ ...itemId ? { id: itemId } : {}
138
+ };
139
+ }
140
+ if (name === "local_shell") {
141
+ const command = stringList(args.command);
142
+ if (!command) return null;
143
+ return {
144
+ type: "local_shell_call",
145
+ id: itemId ?? toolCall.id,
146
+ call_id: toolCall.id,
147
+ status: "completed",
148
+ action: {
149
+ type: "exec",
150
+ command,
151
+ env: stringEnv(args.env),
152
+ timeout_ms: nullableNumber(args.timeout_ms),
153
+ ...typeof args.user === "string" || args.user === null ? { user: args.user } : {},
154
+ ...typeof args.working_directory === "string" || args.working_directory === null ? { working_directory: args.working_directory } : {}
155
+ }
156
+ };
157
+ }
158
+ const commands = stringList(args.commands);
159
+ if (!commands) return null;
160
+ return {
161
+ type: "shell_call",
162
+ call_id: toolCall.id,
163
+ status: "completed",
164
+ action: {
165
+ commands,
166
+ max_output_length: nullableNumber(args.max_output_length),
167
+ timeout_ms: nullableNumber(args.timeout_ms)
168
+ },
169
+ ...itemId ? { id: itemId } : {}
170
+ };
171
+ }
172
+ function shellOutcome(value) {
173
+ if (isRecord(value) && value.type === "timeout") return { type: "timeout" };
174
+ return {
175
+ type: "exit",
176
+ exit_code: isRecord(value) && typeof value.exit_code === "number" ? value.exit_code : 0
177
+ };
178
+ }
179
+ function shellEntry(value) {
180
+ if (typeof value === "string") return {
181
+ stdout: value,
182
+ stderr: "",
183
+ outcome: {
184
+ type: "exit",
185
+ exit_code: 0
186
+ }
187
+ };
188
+ const record = isRecord(value) ? value : {};
189
+ return {
190
+ stdout: typeof record.stdout === "string" ? record.stdout : "",
191
+ stderr: typeof record.stderr === "string" ? record.stderr : "",
192
+ outcome: shellOutcome(record.outcome)
193
+ };
194
+ }
195
+ function shellOutputList(content) {
196
+ const parsed = parseContent(content);
197
+ if (isRecord(parsed) && Array.isArray(parsed.output)) return parsed.output.map((entry) => shellEntry(entry));
198
+ if (isRecord(parsed) && ("stdout" in parsed || "outcome" in parsed)) return [shellEntry(parsed)];
199
+ if (typeof parsed === "string") return [shellEntry(parsed)];
200
+ return [shellEntry(JSON.stringify(parsed ?? ""))];
201
+ }
202
+ /** Replay the app's tool result as the matching Responses output item. */
203
+ function userToolResultItem(toolCall, content) {
204
+ const name = readUserToolName(toolCall.metadata);
205
+ if (!name) return null;
206
+ const parsed = parseContent(content);
207
+ if (name === "apply_patch") {
208
+ const record = isRecord(parsed) ? parsed : {};
209
+ const failed = record.status === "failed" || record.status !== "completed" && typeof record.error === "string";
210
+ const output = typeof record.output === "string" ? record.output : typeof record.error === "string" ? record.error : typeof parsed === "string" ? parsed : void 0;
211
+ return {
212
+ type: "apply_patch_call_output",
213
+ call_id: toolCall.id,
214
+ status: failed ? "failed" : "completed",
215
+ ...output !== void 0 ? { output } : {}
216
+ };
217
+ }
218
+ if (name === "local_shell") {
219
+ const output = typeof parsed === "string" ? parsed : isRecord(parsed) && typeof parsed.output === "string" ? parsed.output : JSON.stringify(parsed ?? "");
220
+ return {
221
+ type: "local_shell_call_output",
222
+ id: toolCall.id,
223
+ output,
224
+ status: "completed"
225
+ };
226
+ }
227
+ const record = isRecord(parsed) ? parsed : {};
228
+ const fromResult = nullableNumber(record.max_output_length);
229
+ const fromCall = readMaxOutputLength(toolCall.metadata);
230
+ const maxOutputLength = record.max_output_length !== void 0 ? fromResult : fromCall;
231
+ return {
232
+ type: "shell_call_output",
233
+ call_id: toolCall.id,
234
+ output: shellOutputList(content),
235
+ ...maxOutputLength !== void 0 ? { max_output_length: maxOutputLength } : {}
236
+ };
237
+ }
238
+ //#endregion
239
+ export { hostedShellCallIds, readUserExecutedCall, readUserToolName, userToolRequestItem, userToolResultItem };
240
+
241
+ //# sourceMappingURL=responses-user-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responses-user-tools.js","names":[],"sources":["../../../src/adapters/responses-user-tools.ts"],"sourcesContent":["import type { ResponseInputItem } from 'openai/resources/responses/responses'\n\n/**\n * OpenAI Responses tools the app must run.\n * A container `shell` runs on the provider. `apply_patch`, `local_shell`,\n * and `shell` with `environment.type: \"local\"` (or no environment) do not.\n */\nexport type OpenAIUserToolName = 'shell' | 'apply_patch' | 'local_shell'\n\nexport interface OpenAIUserExecutedCall {\n name: OpenAIUserToolName\n callId: string\n itemId?: string\n input: Record<string, unknown>\n maxOutputLength?: number | null\n}\n\ninterface ToolCallLike {\n id: string\n function: { name: string; arguments: string }\n metadata?: unknown\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction stringList(value: unknown): Array<string> | null {\n if (\n !Array.isArray(value) ||\n !value.every((entry) => typeof entry === 'string')\n ) {\n return null\n }\n return value\n}\n\nfunction stringEnv(value: unknown): Record<string, string> {\n if (!isRecord(value)) return {}\n const env: Record<string, string> = {}\n for (const [key, entry] of Object.entries(value)) {\n if (typeof entry === 'string') env[key] = entry\n }\n return env\n}\n\nfunction nullableNumber(value: unknown): number | null {\n return typeof value === 'number' || value === null ? value : null\n}\n\n/**\n * Hosted shell calls already include a `shell_call_output` in the same\n * response. Those call ids must not pause the app for another run.\n */\nexport function hostedShellCallIds(\n output: ReadonlyArray<unknown>,\n): Set<string> {\n const ids = new Set<string>()\n for (const item of output) {\n if (\n isRecord(item) &&\n item.type === 'shell_call_output' &&\n typeof item.call_id === 'string'\n ) {\n ids.add(item.call_id)\n }\n }\n return ids\n}\n\nexport function readUserToolName(metadata: unknown): OpenAIUserToolName | null {\n if (!isRecord(metadata)) return null\n const name = metadata.openaiUserTool\n if (name === 'shell' || name === 'apply_patch' || name === 'local_shell') {\n return name\n }\n return null\n}\n\nfunction readItemId(metadata: unknown): string | undefined {\n if (!isRecord(metadata)) return undefined\n return typeof metadata.itemId === 'string' && metadata.itemId.length > 0\n ? metadata.itemId\n : undefined\n}\n\nfunction readMaxOutputLength(metadata: unknown): number | null | undefined {\n if (!isRecord(metadata)) return undefined\n const value = metadata.maxOutputLength\n return typeof value === 'number' || value === null ? value : undefined\n}\n\nfunction applyPatchOperation(\n value: unknown,\n):\n | { type: 'create_file'; path: string; diff: string }\n | { type: 'update_file'; path: string; diff: string }\n | { type: 'delete_file'; path: string }\n | null {\n if (!isRecord(value) || typeof value.path !== 'string') return null\n if (value.type === 'delete_file') {\n return { type: 'delete_file', path: value.path }\n }\n if (\n (value.type === 'create_file' || value.type === 'update_file') &&\n typeof value.diff === 'string'\n ) {\n return { type: value.type, path: value.path, diff: value.diff }\n }\n return null\n}\n\n/**\n * Read a user-run Responses output item.\n * `bareShell` is true only once the full response is known. A shell call\n * with no environment waits for that pass, so a hosted call that later\n * carries `shell_call_output` is not asked of the app.\n */\nexport function readUserExecutedCall(\n item: unknown,\n options: { bareShell: boolean },\n): OpenAIUserExecutedCall | null {\n if (!isRecord(item) || typeof item.type !== 'string') return null\n const callId = typeof item.call_id === 'string' ? item.call_id : ''\n const itemId = typeof item.id === 'string' ? item.id : undefined\n\n if (item.type === 'apply_patch_call') {\n const operation = applyPatchOperation(item.operation)\n if (!callId || !operation) return null\n return {\n name: 'apply_patch',\n callId,\n ...(itemId ? { itemId } : {}),\n input: { operation },\n }\n }\n\n if (item.type === 'local_shell_call') {\n if (!isRecord(item.action)) return null\n const command = stringList(item.action.command)\n if (!callId || !command || command.length === 0) return null\n return {\n name: 'local_shell',\n callId,\n ...(itemId ? { itemId } : {}),\n input: item.action,\n }\n }\n\n if (item.type !== 'shell_call') return null\n const environment = item.environment\n if (isRecord(environment)) {\n if (environment.type !== 'local') return null\n } else if (!options.bareShell) {\n return null\n }\n if (!callId || !isRecord(item.action)) return null\n const commands = stringList(item.action.commands)\n if (!commands || commands.length === 0) return null\n const maxOutputLength = nullableNumber(item.action.max_output_length)\n return {\n name: 'shell',\n callId,\n ...(itemId ? { itemId } : {}),\n input: {\n commands,\n max_output_length: maxOutputLength,\n timeout_ms: nullableNumber(item.action.timeout_ms),\n },\n maxOutputLength,\n }\n}\n\nfunction parseArguments(argumentsString: string): Record<string, unknown> {\n try {\n const parsed: unknown = JSON.parse(argumentsString)\n return isRecord(parsed) ? parsed : {}\n } catch {\n return {}\n }\n}\n\nfunction parseContent(content: unknown): unknown {\n if (typeof content !== 'string') return content\n try {\n return JSON.parse(content) as unknown\n } catch {\n return content\n }\n}\n\n/** Replay a user-run tool call as the Responses input item OpenAI expects. */\nexport function userToolRequestItem(\n toolCall: ToolCallLike,\n): ResponseInputItem | null {\n const name = readUserToolName(toolCall.metadata)\n if (!name) return null\n const args = parseArguments(toolCall.function.arguments)\n const itemId = readItemId(toolCall.metadata)\n\n if (name === 'apply_patch') {\n const operation = applyPatchOperation(args.operation)\n if (!operation) return null\n return {\n type: 'apply_patch_call',\n call_id: toolCall.id,\n status: 'completed',\n operation,\n ...(itemId ? { id: itemId } : {}),\n }\n }\n\n if (name === 'local_shell') {\n const command = stringList(args.command)\n if (!command) return null\n return {\n type: 'local_shell_call',\n id: itemId ?? toolCall.id,\n call_id: toolCall.id,\n status: 'completed',\n action: {\n type: 'exec',\n command,\n env: stringEnv(args.env),\n timeout_ms: nullableNumber(args.timeout_ms),\n ...(typeof args.user === 'string' || args.user === null\n ? { user: args.user }\n : {}),\n ...(typeof args.working_directory === 'string' ||\n args.working_directory === null\n ? { working_directory: args.working_directory }\n : {}),\n },\n }\n }\n\n const commands = stringList(args.commands)\n if (!commands) return null\n return {\n type: 'shell_call',\n call_id: toolCall.id,\n status: 'completed',\n action: {\n commands,\n max_output_length: nullableNumber(args.max_output_length),\n timeout_ms: nullableNumber(args.timeout_ms),\n },\n ...(itemId ? { id: itemId } : {}),\n }\n}\n\ntype ShellOutcome = { type: 'exit'; exit_code: number } | { type: 'timeout' }\n\nfunction shellOutcome(value: unknown): ShellOutcome {\n if (isRecord(value) && value.type === 'timeout') return { type: 'timeout' }\n const exitCode =\n isRecord(value) && typeof value.exit_code === 'number' ? value.exit_code : 0\n return { type: 'exit', exit_code: exitCode }\n}\n\nfunction shellEntry(value: unknown): {\n stdout: string\n stderr: string\n outcome: ShellOutcome\n} {\n if (typeof value === 'string') {\n return {\n stdout: value,\n stderr: '',\n outcome: { type: 'exit', exit_code: 0 },\n }\n }\n const record = isRecord(value) ? value : {}\n return {\n stdout: typeof record.stdout === 'string' ? record.stdout : '',\n stderr: typeof record.stderr === 'string' ? record.stderr : '',\n outcome: shellOutcome(record.outcome),\n }\n}\n\nfunction shellOutputList(content: unknown): Array<{\n stdout: string\n stderr: string\n outcome: ShellOutcome\n}> {\n const parsed = parseContent(content)\n if (isRecord(parsed) && Array.isArray(parsed.output)) {\n return parsed.output.map((entry) => shellEntry(entry))\n }\n if (isRecord(parsed) && ('stdout' in parsed || 'outcome' in parsed)) {\n return [shellEntry(parsed)]\n }\n if (typeof parsed === 'string') return [shellEntry(parsed)]\n return [shellEntry(JSON.stringify(parsed ?? ''))]\n}\n\n/** Replay the app's tool result as the matching Responses output item. */\nexport function userToolResultItem(\n toolCall: ToolCallLike,\n content: unknown,\n): ResponseInputItem | null {\n const name = readUserToolName(toolCall.metadata)\n if (!name) return null\n const parsed = parseContent(content)\n\n if (name === 'apply_patch') {\n const record = isRecord(parsed) ? parsed : {}\n const failed =\n record.status === 'failed' ||\n (record.status !== 'completed' && typeof record.error === 'string')\n const output =\n typeof record.output === 'string'\n ? record.output\n : typeof record.error === 'string'\n ? record.error\n : typeof parsed === 'string'\n ? parsed\n : undefined\n return {\n type: 'apply_patch_call_output',\n call_id: toolCall.id,\n status: failed ? 'failed' : 'completed',\n ...(output !== undefined ? { output } : {}),\n }\n }\n\n if (name === 'local_shell') {\n const output =\n typeof parsed === 'string'\n ? parsed\n : isRecord(parsed) && typeof parsed.output === 'string'\n ? parsed.output\n : JSON.stringify(parsed ?? '')\n return {\n type: 'local_shell_call_output',\n id: toolCall.id,\n output,\n status: 'completed',\n }\n }\n\n const record = isRecord(parsed) ? parsed : {}\n const fromResult = nullableNumber(record.max_output_length)\n const fromCall = readMaxOutputLength(toolCall.metadata)\n const maxOutputLength =\n record.max_output_length !== undefined ? fromResult : fromCall\n return {\n type: 'shell_call_output',\n call_id: toolCall.id,\n output: shellOutputList(content),\n ...(maxOutputLength !== undefined\n ? { max_output_length: maxOutputLength }\n : {}),\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/openai-base",
3
- "version": "0.10.15",
3
+ "version": "0.11.1",
4
4
  "description": "Shared OpenAI SDK base adapters for TanStack AI providers using Chat Completions and Responses APIs.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -47,13 +47,13 @@
47
47
  "@tanstack/ai-utils": "^0.4.1"
48
48
  },
49
49
  "peerDependencies": {
50
- "@tanstack/ai": "^0.58.0"
50
+ "@tanstack/ai": "^0.61.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@vitest/coverage-v8": "4.1.10",
54
54
  "vite": "^8.2.1",
55
55
  "zod": "^4.2.0",
56
- "@tanstack/ai": "0.58.0"
56
+ "@tanstack/ai": "0.61.0"
57
57
  },
58
58
  "scripts": {
59
59
  "build": "vite build",
@@ -1,4 +1,9 @@
1
- import { EventType, normalizeSystemPrompts } from '@tanstack/ai'
1
+ import {
2
+ EventType,
3
+ isFileSource,
4
+ normalizeSystemPrompts,
5
+ unsupportedFileSourceError,
6
+ } from '@tanstack/ai'
2
7
  import { BaseTextAdapter } from '@tanstack/ai/adapters'
3
8
  import {
4
9
  toRunErrorPayload,
@@ -272,7 +277,19 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
272
277
  // rather than letting it cascade into a JSON-parse error on '' — the
273
278
  // root cause (the model returned no content for the structured request)
274
279
  // is then visible in logs.
275
- const rawText = response.choices[0]?.message.content
280
+ const choice = response.choices[0]
281
+
282
+ // A response cut off at the output cap is a truncated JSON document —
283
+ // or, for reasoning models, no content at all once the budget went to
284
+ // reasoning. Report it as truncation before the empty-content and
285
+ // parse errors, which would read like a schema failure (issue #1426).
286
+ if (choice?.finish_reason === 'length') {
287
+ throw new Error(
288
+ `${this.name}.structuredOutput: the response was cut off because the maximum token limit was reached (finish_reason=length); raise the output token limit (max_completion_tokens or max_tokens, depending on the provider)`,
289
+ )
290
+ }
291
+
292
+ const rawText = choice?.message.content
276
293
  if (typeof rawText !== 'string' || rawText.length === 0) {
277
294
  throw new Error(
278
295
  `${this.name}.structuredOutput: response contained no content`,
@@ -346,6 +363,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
346
363
  let hasClosedReasoning = false
347
364
  let stepId: string | undefined
348
365
  let lastModel: string | undefined
366
+ let finishReason: string | null = null
349
367
  let lastUsage:
350
368
  | OpenAI.Chat.Completions.ChatCompletionChunk['usage']
351
369
  | undefined
@@ -486,6 +504,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
486
504
 
487
505
  const choice = chunk.choices[0]
488
506
  if (!choice) continue
507
+ if (choice.finish_reason) finishReason = choice.finish_reason
489
508
 
490
509
  const deltaContent = choice.delta.content
491
510
  if (deltaContent) {
@@ -529,6 +548,22 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
529
548
  }
530
549
  }
531
550
 
551
+ // Same truncation check as `structuredOutput()`: report the token limit
552
+ // before the empty-content and parse errors (issue #1426).
553
+ if (finishReason === 'length') {
554
+ const message = `${this.name}.structuredOutputStream: the response was cut off because the maximum token limit was reached (finish_reason=length); raise the output token limit (max_completion_tokens or max_tokens, depending on the provider)`
555
+ yield {
556
+ type: EventType.RUN_ERROR,
557
+ runId: aguiState.runId,
558
+ model: lastModel || chatOptions.model,
559
+ timestamp: Date.now(),
560
+ message,
561
+ code: 'max_tokens',
562
+ error: { message, code: 'max_tokens' },
563
+ }
564
+ return
565
+ }
566
+
532
567
  if (accumulatedContent.length === 0) {
533
568
  yield {
534
569
  type: EventType.RUN_ERROR,
@@ -1391,6 +1426,22 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
1391
1426
  | { detail?: 'auto' | 'low' | 'high' }
1392
1427
  | undefined
1393
1428
 
1429
+ if (isFileSource(part.source)) {
1430
+ // The Chat Completions API references images only by URL/data URI,
1431
+ // not by an uploaded file_id. Only mention the Responses alternative
1432
+ // for OpenAI itself — compatible providers built on this base have no
1433
+ // file_id-consuming endpoint at all.
1434
+ throw unsupportedFileSourceError(
1435
+ this.name,
1436
+ // Only OpenAI's own chat-completions adapter (name 'openai-chat')
1437
+ // has a Responses sibling to point at; other subclasses (Groq,
1438
+ // Bedrock, BytePlus, compatible providers) have no file_id surface.
1439
+ this.name === 'openai-chat'
1440
+ ? 'on the Chat Completions API — use the Responses adapter (openaiText) to reference an uploaded file by file_id'
1441
+ : 'on the Chat Completions API',
1442
+ )
1443
+ }
1444
+
1394
1445
  // For base64 data, construct a data URI using the mimeType from source.
1395
1446
  // Default to a generic octet-stream MIME if the source didn't provide
1396
1447
  // one — interpolating `undefined` into the URI ("data:undefined;base64,