@swifty.js/swifty 0.0.21 → 0.0.23
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/{agent-ZCMBUWLZ.js → agent-RABW3Z3R.js} +1 -1
- package/dist/anthropic-V22T6E6E.js +4 -0
- package/dist/{checker-OJFS2MZY.js → checker-3RWAYF2U.js} +1 -1
- package/dist/{chunk-Y5WGBAGP.js → chunk-5KXBVL2A.js} +1 -1
- package/dist/{chunk-MUPVOATV.js → chunk-CV7OKXYC.js} +1 -1
- package/dist/chunk-MJR2C7S7.js +130 -0
- package/dist/{chunk-GUXBLUNR.js → chunk-NR62AA5K.js} +1 -1
- package/dist/{chunk-S6ZADC7C.js → chunk-OCQ6TYLT.js} +21 -20
- package/dist/chunk-QN27QEFS.js +4 -0
- package/dist/{chunk-LX2LK3TF.js → chunk-SIWLMWHH.js} +12 -11
- package/dist/chunk-Y43POSIW.js +389 -0
- package/dist/lib/agent-GHXYOARN.js +9 -0
- package/dist/lib/anthropic-7YEN2QTU.js +22 -0
- package/dist/lib/bwrap-QRGPUP4J.js +6 -0
- package/dist/lib/checker-6BW4222R.js +19 -0
- package/dist/lib/chunk-7IZEK7X5.js +545 -0
- package/dist/lib/chunk-BN4Q54G2.js +1096 -0
- package/dist/lib/chunk-BO76JDFZ.js +617 -0
- package/dist/lib/chunk-CYDNMEBC.js +339 -0
- package/dist/lib/chunk-EY7HE52Q.js +384 -0
- package/dist/lib/chunk-GHF2PSEW.js +8 -0
- package/dist/lib/chunk-GNI7YX6F.js +426 -0
- package/dist/lib/chunk-HK2Z6WP4.js +223 -0
- package/dist/lib/chunk-MF3YLQDS.js +1277 -0
- package/dist/lib/chunk-OO2CLOEE.js +88 -0
- package/dist/lib/chunk-ORSYNBMM.js +38 -0
- package/dist/lib/chunk-PZ42NAFA.js +242 -0
- package/dist/lib/chunk-VUD72RTY.js +39 -0
- package/dist/lib/glob.wasm +0 -0
- package/dist/lib/index.d.ts +5347 -0
- package/dist/lib/index.js +11990 -0
- package/dist/lib/openai-RDHZFJUT.js +15 -0
- package/dist/lib/seatbelt-FT5IY73W.js +6 -0
- package/dist/lib/tool-filter-VF7TZRE5.js +19 -0
- package/dist/main.js +204 -466
- package/dist/{openai-HXD52MZB.js → openai-6NWVJ5LI.js} +15 -15
- package/dist/{server-VMHWEO2Y.js → server-KQTLAXC6.js} +17 -17
- package/package.json +18 -6
- package/dist/anthropic-4ZVG7AMA.js +0 -4
- package/dist/chunk-6E6UA7MT.js +0 -126
- package/dist/chunk-DDBH5AEN.js +0 -386
- package/dist/chunk-ZZQE743W.js +0 -4
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getMaxOutputTokens,
|
|
3
|
+
resolveAPIKey
|
|
4
|
+
} from "./chunk-CYDNMEBC.js";
|
|
5
|
+
import {
|
|
6
|
+
AuthenticationError,
|
|
7
|
+
ContextTooLongError,
|
|
8
|
+
LLMError,
|
|
9
|
+
NetworkError,
|
|
10
|
+
RateLimitError,
|
|
11
|
+
ensureToolPairing
|
|
12
|
+
} from "./chunk-OO2CLOEE.js";
|
|
13
|
+
import {
|
|
14
|
+
asRecord,
|
|
15
|
+
asString,
|
|
16
|
+
contentToText,
|
|
17
|
+
createChildLogger,
|
|
18
|
+
isRecord,
|
|
19
|
+
strArg
|
|
20
|
+
} from "./chunk-EY7HE52Q.js";
|
|
21
|
+
|
|
22
|
+
// src/llm/openai.ts
|
|
23
|
+
import OpenAI from "openai";
|
|
24
|
+
var log = createChildLogger({ module: "llm" });
|
|
25
|
+
var OpenAIClient = class {
|
|
26
|
+
client;
|
|
27
|
+
model;
|
|
28
|
+
systemPrompt;
|
|
29
|
+
maxOutputTokens;
|
|
30
|
+
constructor(config, systemPrompt) {
|
|
31
|
+
const apiKey = resolveAPIKey(config);
|
|
32
|
+
if (!apiKey) {
|
|
33
|
+
throw new AuthenticationError(
|
|
34
|
+
"OpenAI API key not found, set OPENAI_API_KEY in .swifty/config.y(a)ml, or via OPENAI_API_KEY env variable."
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
this.client = new OpenAI({
|
|
38
|
+
apiKey,
|
|
39
|
+
baseURL: config.base_url
|
|
40
|
+
});
|
|
41
|
+
this.model = config.model;
|
|
42
|
+
this.systemPrompt = systemPrompt;
|
|
43
|
+
this.maxOutputTokens = getMaxOutputTokens(config);
|
|
44
|
+
}
|
|
45
|
+
async *stream(conversation, toolSchemas, abortSignal) {
|
|
46
|
+
const messages = buildOpenAIInput(ensureToolPairing(conversation.getMessages()));
|
|
47
|
+
const input = [];
|
|
48
|
+
input.push({
|
|
49
|
+
role: "system",
|
|
50
|
+
content: this.systemPrompt
|
|
51
|
+
});
|
|
52
|
+
for (const message of messages) {
|
|
53
|
+
input.push(message);
|
|
54
|
+
}
|
|
55
|
+
const tools = toolSchemas.map((s) => {
|
|
56
|
+
const schema = s.input_schema;
|
|
57
|
+
return {
|
|
58
|
+
type: "function",
|
|
59
|
+
name: s.name,
|
|
60
|
+
description: s.description,
|
|
61
|
+
parameters: schema,
|
|
62
|
+
strict: false
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
const params = {
|
|
66
|
+
model: this.model,
|
|
67
|
+
input,
|
|
68
|
+
stream: true,
|
|
69
|
+
max_output_tokens: this.maxOutputTokens,
|
|
70
|
+
...tools.length > 0 ? { tools } : {}
|
|
71
|
+
};
|
|
72
|
+
let inputTokens = 0;
|
|
73
|
+
let outputTokens = 0;
|
|
74
|
+
let cacheReadInputTokens = 0;
|
|
75
|
+
const cacheCreationInputTokens = 0;
|
|
76
|
+
try {
|
|
77
|
+
const stream = await this.client.responses.create(params, {
|
|
78
|
+
...abortSignal ? { signal: abortSignal } : {}
|
|
79
|
+
});
|
|
80
|
+
let currentToolName = "";
|
|
81
|
+
let currentToolId = "";
|
|
82
|
+
let jsonAccumulate = "";
|
|
83
|
+
let reasoningId = "";
|
|
84
|
+
let reasoningText = "";
|
|
85
|
+
for await (const event of stream) {
|
|
86
|
+
if (event.type === "response.output_text.delta") {
|
|
87
|
+
yield {
|
|
88
|
+
type: "text_delta",
|
|
89
|
+
text: event.delta
|
|
90
|
+
};
|
|
91
|
+
} else if (event.type === "response.reasoning_summary_text.delta") {
|
|
92
|
+
reasoningText += event.delta;
|
|
93
|
+
yield { type: "thinking_delta", text: event.delta };
|
|
94
|
+
} else if (event.type === "response.reasoning_summary_text.done") {
|
|
95
|
+
yield {
|
|
96
|
+
type: "thinking_complete",
|
|
97
|
+
thinking: reasoningText,
|
|
98
|
+
signature: reasoningId
|
|
99
|
+
};
|
|
100
|
+
} else if (event.type === "response.function_call_arguments.delta") {
|
|
101
|
+
jsonAccumulate += event.delta;
|
|
102
|
+
yield {
|
|
103
|
+
type: "tool_call_delta",
|
|
104
|
+
text: event.delta
|
|
105
|
+
};
|
|
106
|
+
} else if (event.type === "response.output_item.added") {
|
|
107
|
+
if (event.item.type === "function_call") {
|
|
108
|
+
currentToolName = event.item.name;
|
|
109
|
+
currentToolId = event.item.call_id;
|
|
110
|
+
jsonAccumulate = "";
|
|
111
|
+
yield {
|
|
112
|
+
type: "tool_call_start",
|
|
113
|
+
toolName: currentToolName,
|
|
114
|
+
toolId: currentToolId
|
|
115
|
+
};
|
|
116
|
+
} else if (event.item.type === "reasoning") {
|
|
117
|
+
reasoningId = event.item.id ?? "";
|
|
118
|
+
reasoningText = "";
|
|
119
|
+
}
|
|
120
|
+
} else if (event.type === "response.output_item.done") {
|
|
121
|
+
if (event.item.type === "function_call" && currentToolName) {
|
|
122
|
+
let args = {};
|
|
123
|
+
if (jsonAccumulate) {
|
|
124
|
+
try {
|
|
125
|
+
const parsed = JSON.parse(jsonAccumulate);
|
|
126
|
+
args = isRecord(parsed) ? asRecord(parsed) : {};
|
|
127
|
+
} catch (err) {
|
|
128
|
+
log.error({ err }, "llm operation failed");
|
|
129
|
+
args = {};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
yield {
|
|
133
|
+
type: "tool_call_complete",
|
|
134
|
+
toolId: currentToolId,
|
|
135
|
+
toolName: currentToolName,
|
|
136
|
+
arguments: args
|
|
137
|
+
};
|
|
138
|
+
currentToolName = "";
|
|
139
|
+
currentToolId = "";
|
|
140
|
+
jsonAccumulate = "";
|
|
141
|
+
}
|
|
142
|
+
} else if (event.type === "response.completed") {
|
|
143
|
+
const usage = event.response.usage;
|
|
144
|
+
if (usage) {
|
|
145
|
+
outputTokens = usage.output_tokens;
|
|
146
|
+
cacheReadInputTokens = usage.input_tokens_details.cached_tokens;
|
|
147
|
+
inputTokens = Math.max(0, usage.input_tokens - cacheReadInputTokens);
|
|
148
|
+
}
|
|
149
|
+
let stopReason = "end_turn";
|
|
150
|
+
const resp = event.response;
|
|
151
|
+
if (resp.status === "incomplete") {
|
|
152
|
+
const details = resp.incomplete_details;
|
|
153
|
+
if (details?.reason === "max_output_tokens") {
|
|
154
|
+
stopReason = "max_tokens";
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
yield {
|
|
158
|
+
type: "stream_end",
|
|
159
|
+
stopReason,
|
|
160
|
+
usage: {
|
|
161
|
+
inputTokens,
|
|
162
|
+
outputTokens,
|
|
163
|
+
cacheReadInputTokens,
|
|
164
|
+
cacheCreationInputTokens
|
|
165
|
+
// 0
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} catch (err) {
|
|
171
|
+
log.error({ err }, "llm operation failed");
|
|
172
|
+
throw classifyOpenAIError(err);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
setSystemPrompt(prompt) {
|
|
176
|
+
this.systemPrompt = prompt;
|
|
177
|
+
}
|
|
178
|
+
setMaxOutputTokens(maxTokens) {
|
|
179
|
+
this.maxOutputTokens = maxTokens;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
function imageDataUrl(block) {
|
|
183
|
+
if (!isRecord(block) || block.type !== "image" || !isRecord(block.source)) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
if (block.source.type === "url") {
|
|
187
|
+
return strArg(block.source, "url") || null;
|
|
188
|
+
}
|
|
189
|
+
if (block.source.type !== "base64") {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
const mediaType = strArg(block.source, "media_type");
|
|
193
|
+
const data = strArg(block.source, "data");
|
|
194
|
+
return mediaType && data ? `data:${mediaType};base64,${data}` : null;
|
|
195
|
+
}
|
|
196
|
+
function documentForResponses(block) {
|
|
197
|
+
if (!isRecord(block) || block.type !== "document" || !isRecord(block.source)) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
const title = typeof block.title === "string" && block.title ? block.title : "tool-result";
|
|
201
|
+
if (block.source.type === "url") {
|
|
202
|
+
const fileUrl = strArg(block.source, "url");
|
|
203
|
+
return fileUrl ? { type: "input_file", file_url: fileUrl } : null;
|
|
204
|
+
}
|
|
205
|
+
if (block.source.type === "base64") {
|
|
206
|
+
const data = strArg(block.source, "data");
|
|
207
|
+
return data ? { type: "input_file", file_data: data, filename: `${title}.pdf` } : null;
|
|
208
|
+
}
|
|
209
|
+
if (block.source.type === "text") {
|
|
210
|
+
const data = strArg(block.source, "data");
|
|
211
|
+
return data ? {
|
|
212
|
+
type: "input_file",
|
|
213
|
+
file_data: Buffer.from(data, "utf-8").toString("base64"),
|
|
214
|
+
filename: `${title}.txt`
|
|
215
|
+
} : null;
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
function documentForChat(block) {
|
|
220
|
+
if (!isRecord(block) || block.type !== "document" || !isRecord(block.source)) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
const title = typeof block.title === "string" && block.title ? block.title : "tool-result";
|
|
224
|
+
if (block.source.type === "base64") {
|
|
225
|
+
const data = strArg(block.source, "data");
|
|
226
|
+
return data ? { type: "file", file: { file_data: data, filename: `${title}.pdf` } } : null;
|
|
227
|
+
}
|
|
228
|
+
if (block.source.type === "text") {
|
|
229
|
+
const data = strArg(block.source, "data");
|
|
230
|
+
return data ? {
|
|
231
|
+
type: "file",
|
|
232
|
+
file: {
|
|
233
|
+
file_data: Buffer.from(data, "utf-8").toString("base64"),
|
|
234
|
+
filename: `${title}.txt`
|
|
235
|
+
}
|
|
236
|
+
} : null;
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
function toolOutputForResponses(tr) {
|
|
241
|
+
if (!tr.contentBlocks?.length) {
|
|
242
|
+
return tr.content;
|
|
243
|
+
}
|
|
244
|
+
const rich = [];
|
|
245
|
+
if (tr.content) {
|
|
246
|
+
rich.push({ type: "input_text", text: tr.content });
|
|
247
|
+
}
|
|
248
|
+
for (const block of tr.contentBlocks) {
|
|
249
|
+
const imageUrl = imageDataUrl(block);
|
|
250
|
+
if (imageUrl) {
|
|
251
|
+
rich.push({ type: "input_image", image_url: imageUrl, detail: "auto" });
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const file = documentForResponses(block);
|
|
255
|
+
if (file) {
|
|
256
|
+
rich.push(file);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return rich.length > 0 ? rich : tr.content;
|
|
260
|
+
}
|
|
261
|
+
function collectRichParts(tr) {
|
|
262
|
+
if (!tr.contentBlocks?.length) {
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
const rich = [];
|
|
266
|
+
for (const block of tr.contentBlocks) {
|
|
267
|
+
const imageUrl = imageDataUrl(block);
|
|
268
|
+
if (imageUrl) {
|
|
269
|
+
rich.push({ type: "image_url", image_url: { url: imageUrl } });
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const file = documentForChat(block);
|
|
273
|
+
if (file) {
|
|
274
|
+
rich.push(file);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (rich.length === 0) {
|
|
278
|
+
return [];
|
|
279
|
+
}
|
|
280
|
+
return [{ type: "text", text: `[Rich content returned by tool call ${tr.toolUseId}]` }, ...rich];
|
|
281
|
+
}
|
|
282
|
+
function userContentsFor(content) {
|
|
283
|
+
if (typeof content === "string") {
|
|
284
|
+
return content;
|
|
285
|
+
}
|
|
286
|
+
const parts = [];
|
|
287
|
+
for (const block of content) {
|
|
288
|
+
if (block.type === "text") {
|
|
289
|
+
parts.push({ type: "input_text", text: strArg(block, "text") });
|
|
290
|
+
} else {
|
|
291
|
+
const url = imageDataUrl(block);
|
|
292
|
+
if (url) {
|
|
293
|
+
parts.push({ type: "input_image", image_url: url, detail: "auto" });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return parts;
|
|
298
|
+
}
|
|
299
|
+
function userPartsFor(content) {
|
|
300
|
+
if (typeof content === "string") {
|
|
301
|
+
return content;
|
|
302
|
+
}
|
|
303
|
+
const parts = [];
|
|
304
|
+
for (const block of content) {
|
|
305
|
+
if (block.type === "text") {
|
|
306
|
+
parts.push({ type: "text", text: strArg(block, "text") });
|
|
307
|
+
} else {
|
|
308
|
+
const url = imageDataUrl(block);
|
|
309
|
+
if (url) {
|
|
310
|
+
parts.push({ type: "image_url", image_url: { url } });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return parts;
|
|
315
|
+
}
|
|
316
|
+
function buildOpenAIInput(messages) {
|
|
317
|
+
const result = [];
|
|
318
|
+
for (const m of messages) {
|
|
319
|
+
if (m.thinkingBlocks) {
|
|
320
|
+
for (const tb of m.thinkingBlocks) {
|
|
321
|
+
result.push({
|
|
322
|
+
type: "reasoning",
|
|
323
|
+
id: tb.signature,
|
|
324
|
+
summary: [{ type: "summary_text", text: tb.thinking }]
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (m.toolUses && m.toolUses.length > 0) {
|
|
329
|
+
const assistantText = typeof m.content === "string" ? m.content : contentToText(m.content);
|
|
330
|
+
if (assistantText) {
|
|
331
|
+
result.push({
|
|
332
|
+
role: "assistant",
|
|
333
|
+
content: assistantText
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
for (const tu of m.toolUses) {
|
|
337
|
+
result.push({
|
|
338
|
+
type: "function_call",
|
|
339
|
+
name: tu.toolName,
|
|
340
|
+
call_id: tu.toolUseId,
|
|
341
|
+
arguments: JSON.stringify(tu.arguments)
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
} else if (m.toolResults && m.toolResults.length > 0) {
|
|
345
|
+
for (const tr of m.toolResults) {
|
|
346
|
+
result.push({
|
|
347
|
+
type: "function_call_output",
|
|
348
|
+
call_id: tr.toolUseId,
|
|
349
|
+
output: toolOutputForResponses(tr)
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
} else if (m.role === "assistant") {
|
|
353
|
+
result.push({
|
|
354
|
+
role: "assistant",
|
|
355
|
+
content: typeof m.content === "string" ? m.content : contentToText(m.content)
|
|
356
|
+
});
|
|
357
|
+
} else {
|
|
358
|
+
result.push({
|
|
359
|
+
role: m.role,
|
|
360
|
+
content: userContentsFor(m.content)
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return result;
|
|
365
|
+
}
|
|
366
|
+
function containsContextLengthError(msg) {
|
|
367
|
+
return /context_length_exceeded/i.test(msg) || /maximum\scontext\slength/i.test(msg) || /prompts?\s+too\s+long/i.test(msg);
|
|
368
|
+
}
|
|
369
|
+
var OpenAICompatClient = class {
|
|
370
|
+
client;
|
|
371
|
+
model;
|
|
372
|
+
systemPrompt;
|
|
373
|
+
maxOutputTokens;
|
|
374
|
+
constructor(config, systemPrompt) {
|
|
375
|
+
const apiKey = resolveAPIKey(config);
|
|
376
|
+
if (!apiKey) {
|
|
377
|
+
throw new AuthenticationError(
|
|
378
|
+
"OpenAI API key not found. Set OPENAI_API_KEY in .swifty/config.y(a)ml, or via OPENAI_API_KEY env variable."
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
this.client = new OpenAI({ apiKey, baseURL: config.base_url });
|
|
382
|
+
this.model = config.model;
|
|
383
|
+
this.systemPrompt = systemPrompt;
|
|
384
|
+
this.maxOutputTokens = getMaxOutputTokens(config);
|
|
385
|
+
}
|
|
386
|
+
setSystemPrompt(prompt) {
|
|
387
|
+
this.systemPrompt = prompt;
|
|
388
|
+
}
|
|
389
|
+
setMaxOutputTokens(maxTokens) {
|
|
390
|
+
this.maxOutputTokens = maxTokens;
|
|
391
|
+
}
|
|
392
|
+
async *stream(conversation, toolSchemas, abortSignal) {
|
|
393
|
+
const messages = [
|
|
394
|
+
{
|
|
395
|
+
role: "system",
|
|
396
|
+
content: this.systemPrompt
|
|
397
|
+
},
|
|
398
|
+
...buildChatCompletionMessages(ensureToolPairing(conversation.getMessages()))
|
|
399
|
+
];
|
|
400
|
+
const tools = toolSchemas.map((ts) => ({
|
|
401
|
+
// name: ts.name,
|
|
402
|
+
// description: ts.description,
|
|
403
|
+
type: "function",
|
|
404
|
+
function: {
|
|
405
|
+
name: ts.name,
|
|
406
|
+
description: ts.description,
|
|
407
|
+
parameters: ts.input_schema,
|
|
408
|
+
strict: false
|
|
409
|
+
}
|
|
410
|
+
}));
|
|
411
|
+
const params = {
|
|
412
|
+
model: this.model,
|
|
413
|
+
messages,
|
|
414
|
+
stream: true,
|
|
415
|
+
stream_options: { include_usage: true },
|
|
416
|
+
max_tokens: this.maxOutputTokens,
|
|
417
|
+
...tools.length > 0 ? { tools } : {}
|
|
418
|
+
};
|
|
419
|
+
let inputTokens = 0;
|
|
420
|
+
let outputTokens = 0;
|
|
421
|
+
let cacheReadInputTokens = 0;
|
|
422
|
+
const cacheCreationInputTokens = 0;
|
|
423
|
+
try {
|
|
424
|
+
const stream = await this.client.chat.completions.create(params, {
|
|
425
|
+
...abortSignal ? { signal: abortSignal } : {}
|
|
426
|
+
});
|
|
427
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
428
|
+
let finishReason = null;
|
|
429
|
+
let reasoningAccumulate = "";
|
|
430
|
+
for await (const chunk of stream) {
|
|
431
|
+
if (chunk.usage) {
|
|
432
|
+
outputTokens = chunk.usage.completion_tokens ?? 0;
|
|
433
|
+
cacheReadInputTokens = chunk.usage.prompt_tokens_details?.cached_tokens ?? 0;
|
|
434
|
+
inputTokens = Math.max(0, (chunk.usage.prompt_tokens ?? 0) - cacheReadInputTokens);
|
|
435
|
+
}
|
|
436
|
+
if (chunk.choices.length === 0) {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
const delta = chunk.choices[0].delta;
|
|
440
|
+
if (delta.content) {
|
|
441
|
+
yield { type: "text_delta", text: delta.content };
|
|
442
|
+
}
|
|
443
|
+
const reasoningContent = strArg(asRecord(delta), "reasoning_content");
|
|
444
|
+
if (reasoningContent) {
|
|
445
|
+
reasoningAccumulate += reasoningContent;
|
|
446
|
+
yield { type: "thinking_delta", text: reasoningContent };
|
|
447
|
+
}
|
|
448
|
+
if (delta.tool_calls) {
|
|
449
|
+
for (const tc of delta.tool_calls) {
|
|
450
|
+
if (!toolCalls.has(tc.index)) {
|
|
451
|
+
toolCalls.set(tc.index, {
|
|
452
|
+
id: tc.id ?? "",
|
|
453
|
+
name: tc.function?.name ?? "",
|
|
454
|
+
args: ""
|
|
455
|
+
});
|
|
456
|
+
if (tc.id) {
|
|
457
|
+
yield {
|
|
458
|
+
type: "tool_call_start",
|
|
459
|
+
toolName: tc.function?.name ?? "",
|
|
460
|
+
toolId: tc.id ?? ""
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const existing = toolCalls.get(tc.index);
|
|
465
|
+
if (existing) {
|
|
466
|
+
if (tc.id) {
|
|
467
|
+
existing.id = tc.id;
|
|
468
|
+
}
|
|
469
|
+
if (tc.function?.name) {
|
|
470
|
+
existing.name = tc.function.name;
|
|
471
|
+
}
|
|
472
|
+
if (tc.function?.arguments) {
|
|
473
|
+
existing.args += tc.function.arguments;
|
|
474
|
+
yield {
|
|
475
|
+
type: "tool_call_delta",
|
|
476
|
+
text: tc.function.arguments
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if (chunk.choices[0].finish_reason) {
|
|
483
|
+
finishReason = chunk.choices[0].finish_reason;
|
|
484
|
+
if (reasoningAccumulate) {
|
|
485
|
+
yield {
|
|
486
|
+
type: "thinking_complete",
|
|
487
|
+
thinking: reasoningAccumulate,
|
|
488
|
+
signature: ""
|
|
489
|
+
};
|
|
490
|
+
reasoningAccumulate = "";
|
|
491
|
+
}
|
|
492
|
+
for (const tu of toolCalls.values()) {
|
|
493
|
+
let args = {};
|
|
494
|
+
const jsonArgs = tu.args;
|
|
495
|
+
if (jsonArgs) {
|
|
496
|
+
try {
|
|
497
|
+
const parsed = JSON.parse(jsonArgs);
|
|
498
|
+
args = isRecord(parsed) ? asRecord(parsed) : {};
|
|
499
|
+
} catch (err) {
|
|
500
|
+
log.error({ err }, "llm operation failed");
|
|
501
|
+
args = {};
|
|
502
|
+
}
|
|
503
|
+
yield {
|
|
504
|
+
type: "tool_call_complete",
|
|
505
|
+
toolName: tu.name,
|
|
506
|
+
toolId: tu.id,
|
|
507
|
+
arguments: args
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
let stopReason;
|
|
514
|
+
if (finishReason === "length") {
|
|
515
|
+
stopReason = "max_tokens";
|
|
516
|
+
} else if (finishReason === "tool_calls" || toolCalls.size > 0) {
|
|
517
|
+
stopReason = "tool_use";
|
|
518
|
+
} else {
|
|
519
|
+
stopReason = "end_turn";
|
|
520
|
+
}
|
|
521
|
+
yield {
|
|
522
|
+
type: "stream_end",
|
|
523
|
+
stopReason,
|
|
524
|
+
usage: {
|
|
525
|
+
inputTokens,
|
|
526
|
+
outputTokens,
|
|
527
|
+
cacheReadInputTokens,
|
|
528
|
+
cacheCreationInputTokens
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
} catch (err) {
|
|
532
|
+
log.error({ err }, "llm operation failed");
|
|
533
|
+
throw classifyOpenAIError(err);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
function classifyOpenAIError(err) {
|
|
538
|
+
if (err instanceof OpenAI.APIError) {
|
|
539
|
+
if (err.status === 413 /* PromptTooLong */ || err.status === 400 /* BadRequest */ && containsContextLengthError(err.message)) {
|
|
540
|
+
return new ContextTooLongError(`Context Too Long: ${err.message}`);
|
|
541
|
+
}
|
|
542
|
+
if (err.status === 401 /* InvalidAPIKey */) {
|
|
543
|
+
return new AuthenticationError(`Invalid API key: ${err.message}`);
|
|
544
|
+
}
|
|
545
|
+
if (err.status === 429 /* RateLimitError */) {
|
|
546
|
+
return new RateLimitError(`Rate limit error, please wait.`);
|
|
547
|
+
}
|
|
548
|
+
return new LLMError(`OpenAI API error (${asString(err.status)}): ${err.message}`);
|
|
549
|
+
}
|
|
550
|
+
return new NetworkError(`Network error: ${err instanceof Error ? err.message : asString(err)}`);
|
|
551
|
+
}
|
|
552
|
+
function buildChatCompletionMessages(messages) {
|
|
553
|
+
const params = [];
|
|
554
|
+
for (const m of messages) {
|
|
555
|
+
const reasoning = m.thinkingBlocks?.map((tb) => tb.thinking).join("") ?? "";
|
|
556
|
+
const assistantText = typeof m.content === "string" ? m.content : contentToText(m.content);
|
|
557
|
+
if (m.toolUses && m.toolUses.length > 0) {
|
|
558
|
+
params.push({
|
|
559
|
+
role: "assistant",
|
|
560
|
+
content: assistantText || null,
|
|
561
|
+
tool_calls: m.toolUses.map((tu) => ({
|
|
562
|
+
id: tu.toolUseId,
|
|
563
|
+
type: "function",
|
|
564
|
+
function: {
|
|
565
|
+
name: tu.toolName,
|
|
566
|
+
arguments: JSON.stringify(tu.arguments)
|
|
567
|
+
},
|
|
568
|
+
...reasoning ? {
|
|
569
|
+
reasoning_content: reasoning
|
|
570
|
+
} : {}
|
|
571
|
+
}))
|
|
572
|
+
});
|
|
573
|
+
} else if (m.toolResults && m.toolResults.length > 0) {
|
|
574
|
+
const pendingRichParts = [];
|
|
575
|
+
for (const tr of m.toolResults) {
|
|
576
|
+
params.push({
|
|
577
|
+
role: "tool",
|
|
578
|
+
tool_call_id: tr.toolUseId,
|
|
579
|
+
content: tr.content
|
|
580
|
+
});
|
|
581
|
+
pendingRichParts.push(...collectRichParts(tr));
|
|
582
|
+
}
|
|
583
|
+
if (pendingRichParts.length > 0) {
|
|
584
|
+
params.push({
|
|
585
|
+
role: "user",
|
|
586
|
+
content: pendingRichParts
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
} else if (m.role === "assistant") {
|
|
590
|
+
params.push({
|
|
591
|
+
role: "assistant",
|
|
592
|
+
content: assistantText,
|
|
593
|
+
...reasoning ? {
|
|
594
|
+
reasoning_content: reasoning
|
|
595
|
+
} : {}
|
|
596
|
+
});
|
|
597
|
+
} else if (m.role === "system") {
|
|
598
|
+
params.push({
|
|
599
|
+
role: "system",
|
|
600
|
+
content: typeof m.content === "string" ? m.content : contentToText(m.content)
|
|
601
|
+
});
|
|
602
|
+
} else {
|
|
603
|
+
params.push({
|
|
604
|
+
role: "user",
|
|
605
|
+
content: userPartsFor(m.content)
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return params;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
export {
|
|
613
|
+
OpenAIClient,
|
|
614
|
+
buildOpenAIInput,
|
|
615
|
+
OpenAICompatClient,
|
|
616
|
+
buildChatCompletionMessages
|
|
617
|
+
};
|