@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.
- package/dist/esm/adapters/chat-completions-text.js +23 -2
- package/dist/esm/adapters/chat-completions-text.js.map +1 -1
- package/dist/esm/adapters/responses-text.d.ts +6 -1
- package/dist/esm/adapters/responses-text.js +110 -5
- package/dist/esm/adapters/responses-text.js.map +1 -1
- package/dist/esm/adapters/responses-user-tools.d.ts +42 -0
- package/dist/esm/adapters/responses-user-tools.js +241 -0
- package/dist/esm/adapters/responses-user-tools.js.map +1 -0
- package/package.json +3 -3
- package/src/adapters/chat-completions-text.ts +53 -2
- package/src/adapters/responses-text.ts +197 -11
- package/src/adapters/responses-user-tools.ts +355 -0
|
@@ -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.
|
|
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.
|
|
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.
|
|
56
|
+
"@tanstack/ai": "0.61.0"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
|
59
59
|
"build": "vite build",
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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,
|