@bolt-foundry/gambit 0.8.0 → 0.8.3
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/CHANGELOG.md +82 -2
- package/README.md +31 -9
- package/esm/gambit/simulator-ui/dist/bundle.js +4744 -4360
- package/esm/gambit/simulator-ui/dist/bundle.js.map +4 -4
- package/esm/gambit/simulator-ui/dist/favicon.ico +0 -0
- package/esm/mod.d.ts +7 -3
- package/esm/mod.d.ts.map +1 -1
- package/esm/mod.js +5 -1
- package/esm/src/cli_utils.d.ts +3 -2
- package/esm/src/cli_utils.d.ts.map +1 -1
- package/esm/src/cli_utils.js +43 -27
- package/esm/src/openai_compat.d.ts +63 -0
- package/esm/src/openai_compat.d.ts.map +1 -0
- package/esm/src/openai_compat.js +277 -0
- package/esm/src/providers/google.d.ts +16 -0
- package/esm/src/providers/google.d.ts.map +1 -0
- package/esm/src/providers/google.js +352 -0
- package/esm/src/providers/ollama.d.ts +17 -0
- package/esm/src/providers/ollama.d.ts.map +1 -0
- package/esm/src/providers/ollama.js +509 -0
- package/esm/src/providers/openrouter.d.ts +14 -1
- package/esm/src/providers/openrouter.d.ts.map +1 -1
- package/esm/src/providers/openrouter.js +460 -463
- package/esm/src/server.d.ts +4 -0
- package/esm/src/server.d.ts.map +1 -1
- package/esm/src/server.js +623 -164
- package/esm/src/trace.d.ts.map +1 -1
- package/esm/src/trace.js +3 -6
- package/package.json +2 -2
- package/script/gambit/simulator-ui/dist/bundle.js +4744 -4360
- package/script/gambit/simulator-ui/dist/bundle.js.map +4 -4
- package/script/gambit/simulator-ui/dist/favicon.ico +0 -0
- package/script/mod.d.ts +7 -3
- package/script/mod.d.ts.map +1 -1
- package/script/mod.js +9 -3
- package/script/src/cli_utils.d.ts +3 -2
- package/script/src/cli_utils.d.ts.map +1 -1
- package/script/src/cli_utils.js +42 -26
- package/script/src/openai_compat.d.ts +63 -0
- package/script/src/openai_compat.d.ts.map +1 -0
- package/script/src/openai_compat.js +281 -0
- package/script/src/providers/google.d.ts +16 -0
- package/script/src/providers/google.d.ts.map +1 -0
- package/script/src/providers/google.js +359 -0
- package/script/src/providers/ollama.d.ts +17 -0
- package/script/src/providers/ollama.d.ts.map +1 -0
- package/script/src/providers/ollama.js +551 -0
- package/script/src/providers/openrouter.d.ts +14 -1
- package/script/src/providers/openrouter.d.ts.map +1 -1
- package/script/src/providers/openrouter.js +461 -463
- package/script/src/server.d.ts +4 -0
- package/script/src/server.d.ts.map +1 -1
- package/script/src/server.js +623 -164
- package/script/src/trace.d.ts.map +1 -1
- package/script/src/trace.js +3 -6
- package/esm/src/compat/openai.d.ts +0 -2
- package/esm/src/compat/openai.d.ts.map +0 -1
- package/esm/src/compat/openai.js +0 -1
- package/script/src/compat/openai.d.ts +0 -2
- package/script/src/compat/openai.d.ts.map +0 -1
- package/script/src/compat/openai.js +0 -5
|
@@ -36,84 +36,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.OPENROUTER_PREFIX = void 0;
|
|
39
40
|
exports.createOpenRouterProvider = createOpenRouterProvider;
|
|
40
41
|
const dntShim = __importStar(require("../../_dnt.shims.js"));
|
|
41
42
|
const openai_1 = __importDefault(require("openai"));
|
|
43
|
+
const gambit_core_1 = require("@bolt-foundry/gambit-core");
|
|
42
44
|
const logger = console;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const flushText = () => {
|
|
49
|
-
if (!textParts.length)
|
|
50
|
-
return;
|
|
51
|
-
out.push({ type: "text", text: textParts.join("") });
|
|
52
|
-
textParts.length = 0;
|
|
53
|
-
};
|
|
54
|
-
for (const part of content) {
|
|
55
|
-
switch (part.type) {
|
|
56
|
-
case "input_image":
|
|
57
|
-
if (part.image_url) {
|
|
58
|
-
flushText();
|
|
59
|
-
out.push({
|
|
60
|
-
type: "image_url",
|
|
61
|
-
image_url: {
|
|
62
|
-
url: part.image_url,
|
|
63
|
-
detail: part.detail,
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
break;
|
|
68
|
-
case "input_file": {
|
|
69
|
-
const label = part.file_url ?? part.filename;
|
|
70
|
-
if (label)
|
|
71
|
-
textParts.push(`[file] ${label}`);
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
case "input_video":
|
|
75
|
-
textParts.push(`[video] ${part.video_url}`);
|
|
76
|
-
break;
|
|
77
|
-
case "refusal":
|
|
78
|
-
textParts.push(part.refusal);
|
|
79
|
-
break;
|
|
80
|
-
case "input_text":
|
|
81
|
-
case "output_text":
|
|
82
|
-
case "text":
|
|
83
|
-
case "summary_text":
|
|
84
|
-
case "reasoning_text":
|
|
85
|
-
textParts.push(part.text);
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
if (!out.length)
|
|
90
|
-
return textParts.join("");
|
|
91
|
-
flushText();
|
|
92
|
-
return out;
|
|
93
|
-
}
|
|
94
|
-
function contentPartsToText(content) {
|
|
95
|
-
if (typeof content === "string" || content === null)
|
|
96
|
-
return content;
|
|
97
|
-
return content.map((part) => {
|
|
98
|
-
switch (part.type) {
|
|
99
|
-
case "input_text":
|
|
100
|
-
case "output_text":
|
|
101
|
-
case "text":
|
|
102
|
-
case "summary_text":
|
|
103
|
-
case "reasoning_text":
|
|
104
|
-
return part.text;
|
|
105
|
-
case "refusal":
|
|
106
|
-
return part.refusal;
|
|
107
|
-
case "input_file": {
|
|
108
|
-
const label = part.file_url ?? part.filename;
|
|
109
|
-
return label ? `[file] ${label}` : "";
|
|
110
|
-
}
|
|
111
|
-
case "input_video":
|
|
112
|
-
return `[video] ${part.video_url}`;
|
|
113
|
-
default:
|
|
114
|
-
return "";
|
|
115
|
-
}
|
|
116
|
-
}).join("");
|
|
45
|
+
exports.OPENROUTER_PREFIX = "openrouter/";
|
|
46
|
+
function normalizeOpenRouterModel(model) {
|
|
47
|
+
return model.startsWith(exports.OPENROUTER_PREFIX)
|
|
48
|
+
? model.slice(exports.OPENROUTER_PREFIX.length)
|
|
49
|
+
: model;
|
|
117
50
|
}
|
|
118
51
|
function normalizeMessage(content) {
|
|
119
52
|
const toolCalls = content.tool_calls ??
|
|
@@ -132,273 +65,465 @@ function normalizeMessage(content) {
|
|
|
132
65
|
tool_calls: toolCalls && toolCalls.length > 0 ? toolCalls : undefined,
|
|
133
66
|
};
|
|
134
67
|
}
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
68
|
+
function safeJson(input) {
|
|
69
|
+
try {
|
|
70
|
+
const parsed = JSON.parse(input);
|
|
71
|
+
if (parsed && typeof parsed === "object") {
|
|
72
|
+
return parsed;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// fall through
|
|
77
|
+
}
|
|
78
|
+
return {};
|
|
79
|
+
}
|
|
80
|
+
function isAsyncIterable(value) {
|
|
81
|
+
return Boolean(value &&
|
|
82
|
+
typeof value === "object" &&
|
|
83
|
+
Symbol.asyncIterator in value);
|
|
84
|
+
}
|
|
85
|
+
function mapUsage(usage) {
|
|
86
|
+
if (!usage)
|
|
87
|
+
return undefined;
|
|
88
|
+
return {
|
|
89
|
+
promptTokens: usage.input_tokens ?? 0,
|
|
90
|
+
completionTokens: usage.output_tokens ?? 0,
|
|
91
|
+
totalTokens: usage.total_tokens ?? 0,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function mapStatus(status) {
|
|
95
|
+
if (!status)
|
|
96
|
+
return undefined;
|
|
97
|
+
if (status === "completed")
|
|
98
|
+
return "completed";
|
|
99
|
+
if (status === "in_progress" || status === "queued")
|
|
100
|
+
return "in_progress";
|
|
101
|
+
return "failed";
|
|
102
|
+
}
|
|
103
|
+
function mapError(error) {
|
|
104
|
+
if (!error)
|
|
105
|
+
return undefined;
|
|
106
|
+
return { code: error.code, message: error.message };
|
|
107
|
+
}
|
|
108
|
+
function mapTools(tools) {
|
|
109
|
+
if (!tools || tools.length === 0)
|
|
110
|
+
return undefined;
|
|
111
|
+
return tools.map((tool) => ({
|
|
112
|
+
type: "function",
|
|
113
|
+
name: tool.function.name,
|
|
114
|
+
description: tool.function.description ?? null,
|
|
115
|
+
parameters: normalizeToolParameters(tool.function.parameters),
|
|
116
|
+
strict: false,
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
function normalizeToolParameters(parameters) {
|
|
120
|
+
const normalized = structuredClone(parameters ?? {});
|
|
121
|
+
if (normalized.type !== "object") {
|
|
122
|
+
return normalized;
|
|
123
|
+
}
|
|
124
|
+
if (normalized.properties === undefined) {
|
|
125
|
+
normalized.properties = {};
|
|
126
|
+
}
|
|
127
|
+
const props = normalized.properties;
|
|
128
|
+
if (props && typeof props === "object" && !Array.isArray(props)) {
|
|
129
|
+
const requiredKeys = Array.isArray(normalized.required)
|
|
130
|
+
? normalized.required.filter((key) => typeof key === "string" && key in props)
|
|
131
|
+
: [];
|
|
132
|
+
for (const [key, value] of Object.entries(props)) {
|
|
133
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
134
|
+
continue;
|
|
135
|
+
if (!("type" in value))
|
|
136
|
+
continue;
|
|
137
|
+
if (value.type === "object" && value.additionalProperties !== false) {
|
|
138
|
+
props[key] = {
|
|
139
|
+
...value,
|
|
140
|
+
additionalProperties: false,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (requiredKeys.length > 0) {
|
|
145
|
+
normalized.required = requiredKeys;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const additional = normalized.additionalProperties;
|
|
149
|
+
if (additional !== false) {
|
|
150
|
+
normalized.additionalProperties = false;
|
|
151
|
+
}
|
|
152
|
+
return normalized;
|
|
153
|
+
}
|
|
154
|
+
function appendSyntheticTools(tools, input) {
|
|
155
|
+
const needed = new Set();
|
|
156
|
+
for (const item of input) {
|
|
157
|
+
if (item.type !== "function_call")
|
|
158
|
+
continue;
|
|
159
|
+
if (item.name === gambit_core_1.GAMBIT_TOOL_CONTEXT || item.name === gambit_core_1.GAMBIT_TOOL_INIT) {
|
|
160
|
+
needed.add(item.name);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
for (const name of needed) {
|
|
164
|
+
if (tools.some((tool) => tool.name === name))
|
|
165
|
+
continue;
|
|
166
|
+
tools.push({
|
|
167
|
+
type: "function",
|
|
168
|
+
name,
|
|
169
|
+
description: "Synthetic Gambit context payload.",
|
|
170
|
+
parameters: {
|
|
171
|
+
type: "object",
|
|
172
|
+
properties: {},
|
|
173
|
+
additionalProperties: false,
|
|
174
|
+
required: [],
|
|
151
175
|
},
|
|
152
|
-
|
|
153
|
-
|
|
176
|
+
strict: false,
|
|
177
|
+
});
|
|
154
178
|
}
|
|
155
|
-
return items;
|
|
156
179
|
}
|
|
157
|
-
function
|
|
158
|
-
|
|
180
|
+
function mapToolChoice(toolChoice) {
|
|
181
|
+
if (!toolChoice)
|
|
182
|
+
return undefined;
|
|
183
|
+
if (toolChoice === "auto" || toolChoice === "required")
|
|
184
|
+
return toolChoice;
|
|
185
|
+
return { type: "function", name: toolChoice.function.name };
|
|
186
|
+
}
|
|
187
|
+
function mapOpenAIOutputItem(item) {
|
|
188
|
+
const itemType = item.type;
|
|
189
|
+
if (itemType === "message") {
|
|
190
|
+
const message = item;
|
|
191
|
+
const content = [];
|
|
192
|
+
for (const part of message.content ?? []) {
|
|
193
|
+
if (part.type === "output_text") {
|
|
194
|
+
content.push({ type: "output_text", text: part.text });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (content.length === 0)
|
|
198
|
+
return null;
|
|
199
|
+
return {
|
|
200
|
+
type: "message",
|
|
201
|
+
role: "assistant",
|
|
202
|
+
content,
|
|
203
|
+
id: message.id,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (itemType === "function_call") {
|
|
207
|
+
const call = item;
|
|
208
|
+
return {
|
|
209
|
+
type: "function_call",
|
|
210
|
+
call_id: call.call_id,
|
|
211
|
+
name: call.name,
|
|
212
|
+
arguments: call.arguments,
|
|
213
|
+
id: call.id,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
function normalizeOpenAIResponse(response) {
|
|
219
|
+
const outputItems = (response.output ?? [])
|
|
220
|
+
.map(mapOpenAIOutputItem)
|
|
221
|
+
.filter((item) => Boolean(item));
|
|
222
|
+
return {
|
|
223
|
+
id: response.id,
|
|
224
|
+
object: "response",
|
|
225
|
+
model: response.model,
|
|
226
|
+
created: response.created_at,
|
|
227
|
+
status: mapStatus(response.status ?? undefined),
|
|
228
|
+
output: outputItems,
|
|
229
|
+
usage: mapUsage(response.usage),
|
|
230
|
+
error: mapError(response.error),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function toOpenAIInputItems(items) {
|
|
234
|
+
const mapped = [];
|
|
159
235
|
for (const item of items) {
|
|
160
|
-
if (item.type
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
236
|
+
if (item.type === "message") {
|
|
237
|
+
const isAssistant = item.role === "assistant";
|
|
238
|
+
const content = item.content
|
|
239
|
+
.map((part) => {
|
|
240
|
+
if (part.type === "output_text") {
|
|
241
|
+
return {
|
|
242
|
+
type: "output_text",
|
|
243
|
+
text: part.text,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
if (part.type === "input_text") {
|
|
247
|
+
return {
|
|
248
|
+
type: isAssistant ? "output_text" : "input_text",
|
|
249
|
+
text: part.text,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
})
|
|
254
|
+
.filter((part) => Boolean(part));
|
|
255
|
+
if (content.length === 0)
|
|
164
256
|
continue;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
role:
|
|
257
|
+
mapped.push({
|
|
258
|
+
type: "message",
|
|
259
|
+
role: item.role,
|
|
168
260
|
content,
|
|
169
|
-
|
|
261
|
+
id: item.id,
|
|
170
262
|
});
|
|
171
263
|
continue;
|
|
172
264
|
}
|
|
173
|
-
if (item.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
265
|
+
if (item.type === "function_call") {
|
|
266
|
+
mapped.push({
|
|
267
|
+
type: "function_call",
|
|
268
|
+
call_id: item.call_id,
|
|
269
|
+
name: item.name,
|
|
270
|
+
arguments: item.arguments,
|
|
271
|
+
id: item.id,
|
|
180
272
|
});
|
|
181
273
|
continue;
|
|
182
274
|
}
|
|
183
|
-
if (item.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
275
|
+
if (item.type === "function_call_output") {
|
|
276
|
+
mapped.push({
|
|
277
|
+
type: "function_call_output",
|
|
278
|
+
call_id: item.call_id,
|
|
279
|
+
output: item.output,
|
|
280
|
+
id: item.id,
|
|
189
281
|
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return mapped;
|
|
285
|
+
}
|
|
286
|
+
function chatMessagesToResponseItems(messages) {
|
|
287
|
+
const items = [];
|
|
288
|
+
for (const message of messages) {
|
|
289
|
+
if (message.role === "tool") {
|
|
290
|
+
if (message.tool_call_id &&
|
|
291
|
+
typeof message.content === "string") {
|
|
292
|
+
items.push({
|
|
293
|
+
type: "function_call_output",
|
|
294
|
+
call_id: message.tool_call_id,
|
|
295
|
+
output: message.content,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
190
298
|
continue;
|
|
191
299
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
content
|
|
196
|
-
|
|
197
|
-
|
|
300
|
+
if (message.role === "system" || message.role === "user" ||
|
|
301
|
+
message.role === "assistant") {
|
|
302
|
+
const content = [];
|
|
303
|
+
if (typeof message.content === "string" && message.content.length > 0) {
|
|
304
|
+
content.push({
|
|
305
|
+
type: message.role === "assistant" ? "output_text" : "input_text",
|
|
306
|
+
text: message.content,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
if (content.length > 0) {
|
|
310
|
+
items.push({
|
|
311
|
+
type: "message",
|
|
312
|
+
role: message.role,
|
|
313
|
+
content,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (message.role === "assistant" && message.tool_calls) {
|
|
318
|
+
for (const call of message.tool_calls) {
|
|
319
|
+
items.push({
|
|
320
|
+
type: "function_call",
|
|
321
|
+
call_id: call.id,
|
|
322
|
+
name: call.function.name,
|
|
323
|
+
arguments: call.function.arguments,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return items;
|
|
329
|
+
}
|
|
330
|
+
function responseItemsToChat(items) {
|
|
331
|
+
const textParts = [];
|
|
332
|
+
const toolCalls = [];
|
|
333
|
+
const messageToolCalls = [];
|
|
334
|
+
for (const item of items) {
|
|
335
|
+
if (item.type === "message" && item.role === "assistant") {
|
|
336
|
+
for (const part of item.content) {
|
|
337
|
+
if (part.type === "output_text") {
|
|
338
|
+
textParts.push(part.text);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (item.type === "function_call") {
|
|
343
|
+
toolCalls.push({
|
|
344
|
+
id: item.call_id,
|
|
345
|
+
name: item.name,
|
|
346
|
+
args: safeJson(item.arguments),
|
|
347
|
+
});
|
|
348
|
+
messageToolCalls.push({
|
|
349
|
+
id: item.call_id,
|
|
350
|
+
type: "function",
|
|
351
|
+
function: { name: item.name, arguments: item.arguments },
|
|
352
|
+
});
|
|
353
|
+
}
|
|
198
354
|
}
|
|
199
|
-
|
|
355
|
+
const content = textParts.length > 0 ? textParts.join("") : null;
|
|
356
|
+
const message = {
|
|
357
|
+
role: "assistant",
|
|
358
|
+
content,
|
|
359
|
+
tool_calls: messageToolCalls.length > 0 ? messageToolCalls : undefined,
|
|
360
|
+
};
|
|
361
|
+
return {
|
|
362
|
+
message,
|
|
363
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
364
|
+
};
|
|
200
365
|
}
|
|
201
|
-
function
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
366
|
+
async function createResponse(client, request, onStreamEvent) {
|
|
367
|
+
const baseParams = {
|
|
368
|
+
model: normalizeOpenRouterModel(request.model),
|
|
369
|
+
input: toOpenAIInputItems(request.input),
|
|
370
|
+
instructions: request.instructions,
|
|
371
|
+
tools: undefined,
|
|
372
|
+
tool_choice: mapToolChoice(request.tool_choice),
|
|
373
|
+
stream: request.stream,
|
|
374
|
+
max_output_tokens: request.max_output_tokens,
|
|
375
|
+
metadata: request.metadata,
|
|
207
376
|
};
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
377
|
+
const mappedTools = mapTools(request.tools) ?? [];
|
|
378
|
+
appendSyntheticTools(mappedTools, request.input);
|
|
379
|
+
if (mappedTools.length > 0) {
|
|
380
|
+
baseParams.tools = mappedTools;
|
|
381
|
+
}
|
|
382
|
+
const params = { ...(request.params ?? {}), ...baseParams };
|
|
383
|
+
const debugResponses = dntShim.Deno.env.get("GAMBIT_DEBUG_RESPONSES") === "1";
|
|
384
|
+
let responseOrStream;
|
|
385
|
+
try {
|
|
386
|
+
responseOrStream = await client.responses.create(params);
|
|
387
|
+
}
|
|
388
|
+
catch (err) {
|
|
389
|
+
if (debugResponses) {
|
|
390
|
+
logger.error("[responses-debug] request", params);
|
|
391
|
+
if (err instanceof openai_1.default.APIError) {
|
|
392
|
+
logger.error("[responses-debug] error", err.error);
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
logger.error("[responses-debug] error", err);
|
|
396
|
+
}
|
|
220
397
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
398
|
+
throw err;
|
|
399
|
+
}
|
|
400
|
+
if (request.stream &&
|
|
401
|
+
isAsyncIterable(responseOrStream)) {
|
|
402
|
+
let completed = null;
|
|
403
|
+
for await (const event of responseOrStream) {
|
|
404
|
+
if (!event || typeof event !== "object" || !("type" in event)) {
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
switch (event.type) {
|
|
408
|
+
case "response.created": {
|
|
409
|
+
const mapped = normalizeOpenAIResponse(event.response);
|
|
410
|
+
onStreamEvent?.({ type: "response.created", response: mapped });
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
case "response.output_text.delta":
|
|
414
|
+
onStreamEvent?.({
|
|
415
|
+
type: "response.output_text.delta",
|
|
416
|
+
output_index: event.output_index,
|
|
417
|
+
delta: event.delta,
|
|
418
|
+
item_id: event.item_id,
|
|
419
|
+
});
|
|
420
|
+
break;
|
|
421
|
+
case "response.output_text.done":
|
|
422
|
+
onStreamEvent?.({
|
|
423
|
+
type: "response.output_text.done",
|
|
424
|
+
output_index: event.output_index,
|
|
425
|
+
text: event.text,
|
|
426
|
+
item_id: event.item_id,
|
|
427
|
+
});
|
|
428
|
+
break;
|
|
429
|
+
case "response.output_item.added": {
|
|
430
|
+
const item = mapOpenAIOutputItem(event.item);
|
|
431
|
+
if (item) {
|
|
432
|
+
onStreamEvent?.({
|
|
433
|
+
type: "response.output_item.added",
|
|
434
|
+
output_index: event.output_index,
|
|
435
|
+
item,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
case "response.output_item.done": {
|
|
441
|
+
const item = mapOpenAIOutputItem(event.item);
|
|
442
|
+
if (item) {
|
|
443
|
+
onStreamEvent?.({
|
|
444
|
+
type: "response.output_item.done",
|
|
445
|
+
output_index: event.output_index,
|
|
446
|
+
item,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
case "response.completed": {
|
|
452
|
+
completed = normalizeOpenAIResponse(event.response);
|
|
453
|
+
onStreamEvent?.({ type: "response.completed", response: completed });
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
case "response.failed": {
|
|
457
|
+
const error = mapError(event.response?.error ?? undefined);
|
|
458
|
+
onStreamEvent?.({
|
|
459
|
+
type: "response.failed",
|
|
460
|
+
error: error ?? {},
|
|
461
|
+
});
|
|
462
|
+
break;
|
|
226
463
|
}
|
|
227
|
-
:
|
|
464
|
+
default:
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
228
467
|
}
|
|
468
|
+
if (completed)
|
|
469
|
+
return completed;
|
|
470
|
+
throw new Error("OpenRouter responses stream ended without completion.");
|
|
229
471
|
}
|
|
230
|
-
return
|
|
472
|
+
return normalizeOpenAIResponse(responseOrStream);
|
|
231
473
|
}
|
|
232
474
|
function createOpenRouterProvider(opts) {
|
|
233
475
|
const debugStream = dntShim.Deno.env.get("GAMBIT_DEBUG_STREAM") === "1";
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
apiKey: opts.apiKey,
|
|
244
|
-
baseURL: opts.baseURL ?? "https://openrouter.ai/api/v1",
|
|
245
|
-
defaultHeaders: {
|
|
246
|
-
"HTTP-Referer": opts.referer ?? "https://gambit.local",
|
|
247
|
-
"X-Title": opts.title ?? "Gambit CLI",
|
|
248
|
-
},
|
|
249
|
-
});
|
|
250
|
-
const openResponseEventTypes = new Set([
|
|
251
|
-
"response.output_text.delta",
|
|
252
|
-
"response.output_text.done",
|
|
253
|
-
"response.output_item.added",
|
|
254
|
-
"response.output_item.done",
|
|
255
|
-
"response.content_part.added",
|
|
256
|
-
"response.content_part.done",
|
|
257
|
-
"response.function_call_arguments.delta",
|
|
258
|
-
"response.function_call_arguments.done",
|
|
259
|
-
"response.refusal.delta",
|
|
260
|
-
"response.refusal.done",
|
|
261
|
-
"response.reasoning.delta",
|
|
262
|
-
"response.reasoning.done",
|
|
263
|
-
"response.reasoning_summary_text.delta",
|
|
264
|
-
"response.reasoning_summary_text.done",
|
|
265
|
-
"response.reasoning_summary_part.added",
|
|
266
|
-
"response.reasoning_summary_part.done",
|
|
267
|
-
"response.created",
|
|
268
|
-
"response.queued",
|
|
269
|
-
"response.in_progress",
|
|
270
|
-
"response.failed",
|
|
271
|
-
"response.incomplete",
|
|
272
|
-
"response.completed",
|
|
273
|
-
"error",
|
|
274
|
-
]);
|
|
275
|
-
const buildResponsesRequest = (input) => {
|
|
276
|
-
const { params: _params, state: _state, onStreamEvent: _onStreamEvent, ...request } = input;
|
|
277
|
-
return request;
|
|
278
|
-
};
|
|
476
|
+
const client = (opts.client ??
|
|
477
|
+
new openai_1.default({
|
|
478
|
+
apiKey: opts.apiKey,
|
|
479
|
+
baseURL: opts.baseURL ?? "https://openrouter.ai/api/v1",
|
|
480
|
+
defaultHeaders: {
|
|
481
|
+
"HTTP-Referer": opts.referer ?? "https://gambit.local",
|
|
482
|
+
"X-Title": opts.title ?? "Gambit CLI",
|
|
483
|
+
},
|
|
484
|
+
}));
|
|
279
485
|
return {
|
|
280
486
|
async responses(input) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
input.onStreamEvent?.({
|
|
296
|
-
...streamEvent,
|
|
297
|
-
sequence_number: streamEvent.sequence_number ?? ++sequence,
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
if (event.type === "response.completed" ||
|
|
301
|
-
event.type === "response.failed" ||
|
|
302
|
-
event.type === "response.incomplete") {
|
|
303
|
-
terminalResponse = event.response;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
if (!terminalResponse) {
|
|
307
|
-
throw new Error("OpenRouter responses stream ended without terminal response.");
|
|
487
|
+
return await createResponse(client, input.request, input.onStreamEvent);
|
|
488
|
+
},
|
|
489
|
+
async chat(input) {
|
|
490
|
+
const params = input.params ?? {};
|
|
491
|
+
if (opts.enableResponses) {
|
|
492
|
+
const response = await createResponse(client, {
|
|
493
|
+
model: normalizeOpenRouterModel(input.model),
|
|
494
|
+
input: chatMessagesToResponseItems(input.messages),
|
|
495
|
+
tools: input.tools,
|
|
496
|
+
stream: input.stream,
|
|
497
|
+
params,
|
|
498
|
+
}, (event) => {
|
|
499
|
+
if (event.type === "response.output_text.delta") {
|
|
500
|
+
input.onStreamText?.(event.delta);
|
|
308
501
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
return
|
|
502
|
+
});
|
|
503
|
+
const mapped = responseItemsToChat(response.output);
|
|
504
|
+
return {
|
|
505
|
+
message: mapped.message,
|
|
506
|
+
finishReason: mapped.toolCalls ? "tool_calls" : "stop",
|
|
507
|
+
toolCalls: mapped.toolCalls,
|
|
508
|
+
usage: response.usage,
|
|
509
|
+
};
|
|
312
510
|
}
|
|
313
|
-
const items = normalizeInputItems(input.input, input.instructions ?? null);
|
|
314
|
-
const messages = messagesFromResponseItems(items);
|
|
315
|
-
const requestParams = applyRequestParams(input, input.params ?? {});
|
|
316
|
-
const toolChoice = requestParams.tool_choice ?? "auto";
|
|
317
|
-
delete requestParams.tool_choice;
|
|
318
511
|
if (input.stream) {
|
|
319
512
|
if (debugStream) {
|
|
320
|
-
logger.log(`[stream-debug] requesting stream model=${input.model} messages=${messages.length} tools=${input.tools?.length ?? 0}`);
|
|
513
|
+
logger.log(`[stream-debug] requesting stream model=${input.model} messages=${input.messages.length} tools=${input.tools?.length ?? 0}`);
|
|
321
514
|
}
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
};
|
|
332
|
-
const responseSkeletonBase = {
|
|
333
|
-
id: responseId,
|
|
334
|
-
object: "response",
|
|
335
|
-
created_at: createdAt,
|
|
336
|
-
model: input.model,
|
|
337
|
-
previous_response_id: input.previous_response_id ?? null,
|
|
338
|
-
instructions: input.instructions ?? null,
|
|
339
|
-
tool_choice: input.tool_choice,
|
|
340
|
-
truncation: input.truncation,
|
|
341
|
-
parallel_tool_calls: input.parallel_tool_calls,
|
|
342
|
-
text: input.text,
|
|
343
|
-
max_output_tokens: input.max_output_tokens,
|
|
344
|
-
max_tool_calls: input.max_tool_calls,
|
|
345
|
-
store: input.store,
|
|
346
|
-
background: input.background,
|
|
347
|
-
service_tier: input.service_tier,
|
|
348
|
-
metadata: input.metadata,
|
|
349
|
-
safety_identifier: input.safety_identifier,
|
|
350
|
-
prompt_cache_key: input.prompt_cache_key,
|
|
351
|
-
tools: input.tools,
|
|
352
|
-
output: [],
|
|
353
|
-
};
|
|
354
|
-
emit({
|
|
355
|
-
type: "response.queued",
|
|
356
|
-
response: { ...responseSkeletonBase, status: "queued" },
|
|
357
|
-
});
|
|
358
|
-
const responseSkeleton = {
|
|
359
|
-
...responseSkeletonBase,
|
|
360
|
-
status: "in_progress",
|
|
361
|
-
};
|
|
362
|
-
emit({ type: "response.created", response: responseSkeleton });
|
|
363
|
-
emit({ type: "response.in_progress", response: responseSkeleton });
|
|
364
|
-
emit({
|
|
365
|
-
type: "response.output_item.added",
|
|
366
|
-
output_index: 0,
|
|
367
|
-
item: {
|
|
368
|
-
type: "message",
|
|
369
|
-
id: itemId,
|
|
370
|
-
status: "in_progress",
|
|
371
|
-
role: "assistant",
|
|
372
|
-
content: [],
|
|
373
|
-
},
|
|
515
|
+
const stream = await client.chat.completions.create({
|
|
516
|
+
model: normalizeOpenRouterModel(input.model),
|
|
517
|
+
messages: input
|
|
518
|
+
.messages,
|
|
519
|
+
tools: input
|
|
520
|
+
.tools,
|
|
521
|
+
tool_choice: "auto",
|
|
522
|
+
stream: true,
|
|
523
|
+
...params,
|
|
374
524
|
});
|
|
375
|
-
let stream = null;
|
|
376
|
-
try {
|
|
377
|
-
stream = await client.chat.completions.create({
|
|
378
|
-
model: input.model,
|
|
379
|
-
messages: messages,
|
|
380
|
-
tools: input.tools,
|
|
381
|
-
tool_choice: toolChoice,
|
|
382
|
-
stream: true,
|
|
383
|
-
...requestParams,
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
catch (err) {
|
|
387
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
388
|
-
emit({ type: "error", error: { code: "openrouter_error", message } });
|
|
389
|
-
emit({
|
|
390
|
-
type: "response.failed",
|
|
391
|
-
response: {
|
|
392
|
-
...responseSkeleton,
|
|
393
|
-
status: "failed",
|
|
394
|
-
error: { code: "openrouter_error", message },
|
|
395
|
-
},
|
|
396
|
-
});
|
|
397
|
-
throw err;
|
|
398
|
-
}
|
|
399
525
|
let finishReason = null;
|
|
400
526
|
const contentParts = [];
|
|
401
|
-
let contentPartStarted = false;
|
|
402
527
|
const toolCallMap = new Map();
|
|
403
528
|
let chunkCount = 0;
|
|
404
529
|
let streamedChars = 0;
|
|
@@ -412,24 +537,8 @@ function createOpenRouterProvider(opts) {
|
|
|
412
537
|
}
|
|
413
538
|
const delta = choice.delta;
|
|
414
539
|
if (typeof delta.content === "string") {
|
|
415
|
-
if (!contentPartStarted) {
|
|
416
|
-
emit({
|
|
417
|
-
type: "response.content_part.added",
|
|
418
|
-
item_id: itemId,
|
|
419
|
-
output_index: 0,
|
|
420
|
-
content_index: 0,
|
|
421
|
-
part: { type: "output_text", text: "" },
|
|
422
|
-
});
|
|
423
|
-
contentPartStarted = true;
|
|
424
|
-
}
|
|
425
540
|
contentParts.push(delta.content);
|
|
426
|
-
|
|
427
|
-
type: "response.output_text.delta",
|
|
428
|
-
item_id: itemId,
|
|
429
|
-
output_index: 0,
|
|
430
|
-
content_index: 0,
|
|
431
|
-
delta: delta.content,
|
|
432
|
-
});
|
|
541
|
+
input.onStreamText?.(delta.content);
|
|
433
542
|
streamedChars += delta.content.length;
|
|
434
543
|
}
|
|
435
544
|
else if (Array.isArray(delta.content)) {
|
|
@@ -437,24 +546,8 @@ function createOpenRouterProvider(opts) {
|
|
|
437
546
|
.map((c) => (typeof c === "string" ? c : ""))
|
|
438
547
|
.join("");
|
|
439
548
|
if (chunkStr) {
|
|
440
|
-
if (!contentPartStarted) {
|
|
441
|
-
emit({
|
|
442
|
-
type: "response.content_part.added",
|
|
443
|
-
item_id: itemId,
|
|
444
|
-
output_index: 0,
|
|
445
|
-
content_index: 0,
|
|
446
|
-
part: { type: "output_text", text: "" },
|
|
447
|
-
});
|
|
448
|
-
contentPartStarted = true;
|
|
449
|
-
}
|
|
450
549
|
contentParts.push(chunkStr);
|
|
451
|
-
|
|
452
|
-
type: "response.output_text.delta",
|
|
453
|
-
item_id: itemId,
|
|
454
|
-
output_index: 0,
|
|
455
|
-
content_index: 0,
|
|
456
|
-
delta: chunkStr,
|
|
457
|
-
});
|
|
550
|
+
input.onStreamText?.(chunkStr);
|
|
458
551
|
streamedChars += chunkStr.length;
|
|
459
552
|
}
|
|
460
553
|
}
|
|
@@ -465,20 +558,12 @@ function createOpenRouterProvider(opts) {
|
|
|
465
558
|
id: tc.id,
|
|
466
559
|
function: { name: tc.function?.name, arguments: "" },
|
|
467
560
|
};
|
|
468
|
-
if (
|
|
469
|
-
existing.id = tc.id
|
|
470
|
-
crypto.randomUUID().replace(/-/g, "").slice(0, 24);
|
|
471
|
-
}
|
|
561
|
+
if (tc.id)
|
|
562
|
+
existing.id = tc.id;
|
|
472
563
|
if (tc.function?.name)
|
|
473
564
|
existing.function.name = tc.function.name;
|
|
474
565
|
if (tc.function?.arguments) {
|
|
475
566
|
existing.function.arguments += tc.function.arguments;
|
|
476
|
-
emit({
|
|
477
|
-
type: "response.function_call_arguments.delta",
|
|
478
|
-
item_id: existing.id,
|
|
479
|
-
output_index: 0,
|
|
480
|
-
delta: tc.function.arguments,
|
|
481
|
-
});
|
|
482
567
|
}
|
|
483
568
|
toolCallMap.set(idx, existing);
|
|
484
569
|
}
|
|
@@ -494,135 +579,48 @@ function createOpenRouterProvider(opts) {
|
|
|
494
579
|
arguments: tc.function.arguments,
|
|
495
580
|
},
|
|
496
581
|
}));
|
|
497
|
-
for (const call of tool_calls) {
|
|
498
|
-
emit({
|
|
499
|
-
type: "response.function_call_arguments.done",
|
|
500
|
-
item_id: call.id,
|
|
501
|
-
output_index: 0,
|
|
502
|
-
arguments: call.function.arguments,
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
const text = contentParts.length ? contentParts.join("") : "";
|
|
506
|
-
const outputPart = {
|
|
507
|
-
type: "output_text",
|
|
508
|
-
text,
|
|
509
|
-
};
|
|
510
582
|
const message = normalizeMessage({
|
|
511
583
|
role: "assistant",
|
|
512
|
-
content:
|
|
584
|
+
content: contentParts.length ? contentParts.join("") : null,
|
|
513
585
|
tool_calls,
|
|
514
586
|
});
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
tool_call_id: message.tool_call_id,
|
|
523
|
-
tool_calls: message.tool_calls,
|
|
524
|
-
};
|
|
525
|
-
if (contentPartStarted && text.length > 0) {
|
|
526
|
-
emit({
|
|
527
|
-
type: "response.output_text.done",
|
|
528
|
-
item_id: itemId,
|
|
529
|
-
output_index: 0,
|
|
530
|
-
content_index: 0,
|
|
531
|
-
text,
|
|
532
|
-
});
|
|
533
|
-
emit({
|
|
534
|
-
type: "response.content_part.done",
|
|
535
|
-
item_id: itemId,
|
|
536
|
-
output_index: 0,
|
|
537
|
-
content_index: 0,
|
|
538
|
-
part: outputPart,
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
|
-
emit({
|
|
542
|
-
type: "response.output_item.done",
|
|
543
|
-
output_index: 0,
|
|
544
|
-
item: outputItem,
|
|
545
|
-
});
|
|
546
|
-
const completedAt = Math.floor(Date.now() / 1000);
|
|
547
|
-
const status = finishReason === "length" ? "incomplete" : "completed";
|
|
548
|
-
const responseResource = {
|
|
549
|
-
...responseSkeleton,
|
|
550
|
-
completed_at: completedAt,
|
|
551
|
-
status,
|
|
552
|
-
output: [outputItem],
|
|
553
|
-
finishReason: finishReason ?? "stop",
|
|
554
|
-
};
|
|
555
|
-
if (status === "incomplete") {
|
|
556
|
-
emit({
|
|
557
|
-
type: "response.incomplete",
|
|
558
|
-
response: responseResource,
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
else {
|
|
562
|
-
emit({
|
|
563
|
-
type: "response.completed",
|
|
564
|
-
response: responseResource,
|
|
565
|
-
});
|
|
566
|
-
}
|
|
587
|
+
const toolCalls = tool_calls.length > 0
|
|
588
|
+
? tool_calls.map((tc) => ({
|
|
589
|
+
id: tc.id,
|
|
590
|
+
name: tc.function.name,
|
|
591
|
+
args: safeJson(tc.function.arguments),
|
|
592
|
+
}))
|
|
593
|
+
: undefined;
|
|
567
594
|
return {
|
|
568
|
-
|
|
595
|
+
message,
|
|
596
|
+
finishReason: finishReason ?? "stop",
|
|
597
|
+
toolCalls,
|
|
569
598
|
};
|
|
570
599
|
}
|
|
571
600
|
const response = await client.chat.completions.create({
|
|
572
|
-
model: input.model,
|
|
573
|
-
messages:
|
|
574
|
-
|
|
575
|
-
|
|
601
|
+
model: normalizeOpenRouterModel(input.model),
|
|
602
|
+
messages: input
|
|
603
|
+
.messages,
|
|
604
|
+
tools: input
|
|
605
|
+
.tools,
|
|
606
|
+
tool_choice: "auto",
|
|
576
607
|
stream: false,
|
|
577
|
-
...
|
|
608
|
+
...params,
|
|
578
609
|
});
|
|
579
610
|
const choice = response.choices[0];
|
|
580
611
|
const message = choice.message;
|
|
581
612
|
const normalizedMessage = normalizeMessage(message);
|
|
582
|
-
const
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
status: "completed",
|
|
588
|
-
role: normalizedMessage.role,
|
|
589
|
-
content: normalizedMessage.content,
|
|
590
|
-
name: normalizedMessage.name,
|
|
591
|
-
tool_call_id: normalizedMessage.tool_call_id,
|
|
592
|
-
tool_calls: normalizedMessage.tool_calls,
|
|
593
|
-
};
|
|
594
|
-
const finishReason = choice.finish_reason ??
|
|
595
|
-
"stop";
|
|
596
|
-
const status = finishReason === "length" ? "incomplete" : "completed";
|
|
613
|
+
const toolCalls = message.tool_calls?.map((tc) => ({
|
|
614
|
+
id: tc.id,
|
|
615
|
+
name: tc.function.name,
|
|
616
|
+
args: safeJson(tc.function.arguments),
|
|
617
|
+
}));
|
|
597
618
|
return {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
completed_at: createdAt,
|
|
602
|
-
status,
|
|
603
|
-
model: input.model,
|
|
604
|
-
previous_response_id: input.previous_response_id ?? null,
|
|
605
|
-
instructions: input.instructions ?? null,
|
|
606
|
-
tool_choice: input.tool_choice,
|
|
607
|
-
truncation: input.truncation,
|
|
608
|
-
parallel_tool_calls: input.parallel_tool_calls,
|
|
609
|
-
text: input.text,
|
|
610
|
-
max_output_tokens: input.max_output_tokens,
|
|
611
|
-
max_tool_calls: input.max_tool_calls,
|
|
612
|
-
store: input.store,
|
|
613
|
-
background: input.background,
|
|
614
|
-
service_tier: input.service_tier,
|
|
615
|
-
metadata: input.metadata,
|
|
616
|
-
safety_identifier: input.safety_identifier,
|
|
617
|
-
prompt_cache_key: input.prompt_cache_key,
|
|
618
|
-
tools: input.tools,
|
|
619
|
-
output: [outputItem],
|
|
620
|
-
finishReason,
|
|
619
|
+
message: normalizedMessage,
|
|
620
|
+
finishReason: (choice.finish_reason ?? "stop"),
|
|
621
|
+
toolCalls,
|
|
621
622
|
usage: response.usage
|
|
622
623
|
? {
|
|
623
|
-
input_tokens: response.usage.prompt_tokens ?? 0,
|
|
624
|
-
output_tokens: response.usage.completion_tokens ?? 0,
|
|
625
|
-
total_tokens: response.usage.total_tokens ?? 0,
|
|
626
624
|
promptTokens: response.usage.prompt_tokens ?? 0,
|
|
627
625
|
completionTokens: response.usage.completion_tokens ?? 0,
|
|
628
626
|
totalTokens: response.usage.total_tokens ?? 0,
|