@agentskit/adapters 0.13.12 → 0.13.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -3
- package/dist/catalog/index.cjs +197 -60
- package/dist/catalog/index.cjs.map +1 -1
- package/dist/catalog/index.d.cts +1 -1
- package/dist/catalog/index.d.ts +1 -1
- package/dist/catalog/index.js +2 -1
- package/dist/catalog/index.js.map +1 -1
- package/dist/{chunk-77QS3QRT.js → chunk-4KZTWGRB.js} +249 -119
- package/dist/chunk-4KZTWGRB.js.map +1 -0
- package/dist/chunk-627FHZZY.js +101 -0
- package/dist/chunk-627FHZZY.js.map +1 -0
- package/dist/chunk-6CIB2QCH.js +81 -0
- package/dist/chunk-6CIB2QCH.js.map +1 -0
- package/dist/createAdapter-C-pKR1aO.d.cts +19 -0
- package/dist/createAdapter-C-pKR1aO.d.ts +19 -0
- package/dist/createAdapter.cjs +138 -15
- package/dist/createAdapter.cjs.map +1 -1
- package/dist/createAdapter.d.cts +1 -1
- package/dist/createAdapter.d.ts +1 -1
- package/dist/createAdapter.js +2 -1
- package/dist/index.cjs +1090 -319
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -13
- package/dist/index.d.ts +33 -13
- package/dist/index.js +686 -191
- package/dist/index.js.map +1 -1
- package/package.json +11 -6
- package/dist/chunk-6FPLWOVE.js +0 -26
- package/dist/chunk-6FPLWOVE.js.map +0 -1
- package/dist/chunk-77QS3QRT.js.map +0 -1
- package/dist/createAdapter-Dfnf3Iio.d.cts +0 -14
- package/dist/createAdapter-Dfnf3Iio.d.ts +0 -14
package/dist/index.cjs
CHANGED
|
@@ -2,26 +2,175 @@
|
|
|
2
2
|
|
|
3
3
|
var core = require('@agentskit/core');
|
|
4
4
|
|
|
5
|
+
// src/stream-errors.ts
|
|
6
|
+
function isAbortError(err) {
|
|
7
|
+
if (err instanceof DOMException && err.name === "AbortError") return true;
|
|
8
|
+
if (err instanceof Error && err.name === "AbortError") return true;
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
function adapterErrorChunk(message, options) {
|
|
12
|
+
const error = new core.AdapterError({
|
|
13
|
+
code: options?.code ?? core.ErrorCodes.AK_ADAPTER_STREAM_FAILED,
|
|
14
|
+
message,
|
|
15
|
+
cause: options?.cause
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
type: "error",
|
|
19
|
+
content: message,
|
|
20
|
+
metadata: { error }
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function parseCompleteToolArgs(args) {
|
|
24
|
+
const raw = args.length > 0 ? args : "{}";
|
|
25
|
+
try {
|
|
26
|
+
JSON.parse(raw);
|
|
27
|
+
return { ok: true, args: raw };
|
|
28
|
+
} catch (cause) {
|
|
29
|
+
return {
|
|
30
|
+
ok: false,
|
|
31
|
+
error: new core.AdapterError({
|
|
32
|
+
code: core.ErrorCodes.AK_ADAPTER_STREAM_FAILED,
|
|
33
|
+
message: "Incomplete or malformed tool-call arguments JSON",
|
|
34
|
+
cause
|
|
35
|
+
})
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function raceAbort(promise, signal) {
|
|
40
|
+
if (signal.aborted) {
|
|
41
|
+
return Promise.reject(new DOMException("Aborted", "AbortError"));
|
|
42
|
+
}
|
|
43
|
+
return new Promise((resolve2, reject) => {
|
|
44
|
+
let settled = false;
|
|
45
|
+
const onAbort = () => {
|
|
46
|
+
if (settled) return;
|
|
47
|
+
settled = true;
|
|
48
|
+
signal.removeEventListener("abort", onAbort);
|
|
49
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
50
|
+
};
|
|
51
|
+
signal.addEventListener("abort", onAbort);
|
|
52
|
+
promise.then(
|
|
53
|
+
(value) => {
|
|
54
|
+
if (settled) return;
|
|
55
|
+
settled = true;
|
|
56
|
+
signal.removeEventListener("abort", onAbort);
|
|
57
|
+
resolve2(value);
|
|
58
|
+
},
|
|
59
|
+
(err) => {
|
|
60
|
+
if (settled) return;
|
|
61
|
+
settled = true;
|
|
62
|
+
signal.removeEventListener("abort", onAbort);
|
|
63
|
+
reject(err);
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async function abortableSleep(ms, signal, sleep) {
|
|
69
|
+
if (signal.aborted) throw new DOMException("Aborted", "AbortError");
|
|
70
|
+
await raceAbort(sleep(ms), signal);
|
|
71
|
+
if (signal.aborted) throw new DOMException("Aborted", "AbortError");
|
|
72
|
+
}
|
|
73
|
+
async function cancelBody(body) {
|
|
74
|
+
if (!body) return;
|
|
75
|
+
try {
|
|
76
|
+
await body.cancel();
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
5
81
|
// src/createAdapter.ts
|
|
6
82
|
function createAdapter(config) {
|
|
7
83
|
return {
|
|
8
|
-
createSource: (request) =>
|
|
9
|
-
|
|
84
|
+
createSource: (request) => {
|
|
85
|
+
const controller = new AbortController();
|
|
86
|
+
let activeStream = null;
|
|
87
|
+
let parseIter = null;
|
|
88
|
+
const cleanup = () => {
|
|
10
89
|
try {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
yield* config.parse(stream);
|
|
14
|
-
} catch (err) {
|
|
15
|
-
yield {
|
|
16
|
-
type: "error",
|
|
17
|
-
content: err instanceof Error ? err.message : String(err)
|
|
18
|
-
};
|
|
90
|
+
void activeStream?.cancel();
|
|
91
|
+
} catch {
|
|
19
92
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
93
|
+
activeStream = null;
|
|
94
|
+
if (parseIter && typeof parseIter.return === "function") {
|
|
95
|
+
void parseIter.return(void 0).catch(() => {
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
parseIter = null;
|
|
99
|
+
try {
|
|
100
|
+
config.abort?.();
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
return {
|
|
105
|
+
stream: async function* () {
|
|
106
|
+
if (controller.signal.aborted) return;
|
|
107
|
+
try {
|
|
108
|
+
const result = await raceAbort(config.send(request, controller.signal), controller.signal);
|
|
109
|
+
if (controller.signal.aborted) {
|
|
110
|
+
if (result instanceof Response) {
|
|
111
|
+
try {
|
|
112
|
+
await result.body?.cancel();
|
|
113
|
+
} catch {
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const stream = result instanceof Response ? result.body : result;
|
|
119
|
+
if (!stream) {
|
|
120
|
+
yield adapterErrorChunk("Adapter send() returned empty body");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
activeStream = stream;
|
|
124
|
+
const response = result instanceof Response ? result : void 0;
|
|
125
|
+
const iterable = config.parse(stream, response);
|
|
126
|
+
parseIter = iterable[Symbol.asyncIterator]();
|
|
127
|
+
while (true) {
|
|
128
|
+
if (controller.signal.aborted) {
|
|
129
|
+
cleanup();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
let next;
|
|
133
|
+
try {
|
|
134
|
+
next = await raceAbort(parseIter.next(), controller.signal);
|
|
135
|
+
} catch (err) {
|
|
136
|
+
if (isAbortError(err) || controller.signal.aborted) {
|
|
137
|
+
cleanup();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
if (next.done) break;
|
|
143
|
+
if (controller.signal.aborted) {
|
|
144
|
+
cleanup();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
yield next.value;
|
|
148
|
+
if (next.value.type === "done" || next.value.type === "error") {
|
|
149
|
+
if (typeof parseIter.return === "function") {
|
|
150
|
+
await parseIter.return(void 0).catch(() => {
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
parseIter = null;
|
|
154
|
+
activeStream = null;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
yield adapterErrorChunk("Adapter parser ended without a terminal chunk");
|
|
159
|
+
activeStream = null;
|
|
160
|
+
} catch (err) {
|
|
161
|
+
if (isAbortError(err) || controller.signal.aborted) return;
|
|
162
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
163
|
+
yield adapterErrorChunk(message, { cause: err });
|
|
164
|
+
} finally {
|
|
165
|
+
if (controller.signal.aborted) cleanup();
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
abort: () => {
|
|
169
|
+
if (!controller.signal.aborted) controller.abort();
|
|
170
|
+
cleanup();
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
25
174
|
};
|
|
26
175
|
}
|
|
27
176
|
|
|
@@ -29,73 +178,67 @@ function createAdapter(config) {
|
|
|
29
178
|
function generic(config) {
|
|
30
179
|
return {
|
|
31
180
|
createSource: (request) => {
|
|
181
|
+
const controller = new AbortController();
|
|
32
182
|
let reader = null;
|
|
33
183
|
const decoder = new TextDecoder();
|
|
34
184
|
return {
|
|
35
185
|
stream: async function* () {
|
|
186
|
+
if (controller.signal.aborted) return;
|
|
36
187
|
try {
|
|
37
|
-
const stream = await
|
|
188
|
+
const stream = await raceAbort(
|
|
189
|
+
config.send(request, controller.signal),
|
|
190
|
+
controller.signal
|
|
191
|
+
);
|
|
192
|
+
if (controller.signal.aborted) {
|
|
193
|
+
try {
|
|
194
|
+
await stream.cancel();
|
|
195
|
+
} catch {
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
38
199
|
reader = stream.getReader();
|
|
39
200
|
while (true) {
|
|
40
|
-
|
|
201
|
+
if (controller.signal.aborted) {
|
|
202
|
+
try {
|
|
203
|
+
await reader.cancel();
|
|
204
|
+
} catch {
|
|
205
|
+
}
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const { done, value } = await raceAbort(reader.read(), controller.signal);
|
|
41
209
|
if (done) break;
|
|
42
210
|
const text = decoder.decode(value, { stream: true });
|
|
43
211
|
yield { type: "text", content: text };
|
|
44
212
|
}
|
|
213
|
+
if (controller.signal.aborted) return;
|
|
45
214
|
yield { type: "done" };
|
|
46
215
|
} catch (err) {
|
|
216
|
+
if (isAbortError(err) || controller.signal.aborted) return;
|
|
47
217
|
yield {
|
|
48
218
|
type: "error",
|
|
49
|
-
content: err instanceof Error ? err.message : String(err)
|
|
219
|
+
content: err instanceof Error ? err.message : String(err),
|
|
220
|
+
metadata: {
|
|
221
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
222
|
+
}
|
|
50
223
|
};
|
|
51
224
|
} finally {
|
|
52
|
-
|
|
225
|
+
try {
|
|
226
|
+
reader?.releaseLock();
|
|
227
|
+
} catch {
|
|
228
|
+
}
|
|
53
229
|
}
|
|
54
230
|
},
|
|
55
231
|
abort: () => {
|
|
56
|
-
|
|
232
|
+
if (!controller.signal.aborted) controller.abort();
|
|
233
|
+
void reader?.cancel().catch(() => {
|
|
234
|
+
});
|
|
57
235
|
}
|
|
58
236
|
};
|
|
59
237
|
}
|
|
60
238
|
};
|
|
61
239
|
}
|
|
62
240
|
|
|
63
|
-
// src/
|
|
64
|
-
function toProviderMessages(messages) {
|
|
65
|
-
const knownToolCallIds = /* @__PURE__ */ new Set();
|
|
66
|
-
const output = [];
|
|
67
|
-
for (const message of messages) {
|
|
68
|
-
if (message.role === "tool") {
|
|
69
|
-
const id = message.toolCallId;
|
|
70
|
-
if (!id || !knownToolCallIds.has(id)) continue;
|
|
71
|
-
output.push({
|
|
72
|
-
role: "tool",
|
|
73
|
-
content: message.content,
|
|
74
|
-
tool_call_id: id
|
|
75
|
-
});
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
if (message.role === "assistant" && message.toolCalls && message.toolCalls.length > 0) {
|
|
79
|
-
for (const tc of message.toolCalls) knownToolCallIds.add(tc.id);
|
|
80
|
-
output.push({
|
|
81
|
-
role: "assistant",
|
|
82
|
-
content: message.content || null,
|
|
83
|
-
tool_calls: message.toolCalls.map((tc) => ({
|
|
84
|
-
id: tc.id,
|
|
85
|
-
type: "function",
|
|
86
|
-
function: {
|
|
87
|
-
name: tc.name,
|
|
88
|
-
arguments: typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args ?? {})
|
|
89
|
-
}
|
|
90
|
-
}))
|
|
91
|
-
});
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
if (message.role === "assistant" && !message.content) continue;
|
|
95
|
-
output.push({ role: message.role, content: message.content });
|
|
96
|
-
}
|
|
97
|
-
return output;
|
|
98
|
-
}
|
|
241
|
+
// src/stream-lines.ts
|
|
99
242
|
async function* readSSELines(stream) {
|
|
100
243
|
const reader = stream.getReader();
|
|
101
244
|
const decoder = new TextDecoder();
|
|
@@ -137,23 +280,61 @@ async function* readNDJSONLines(stream) {
|
|
|
137
280
|
reader.releaseLock();
|
|
138
281
|
}
|
|
139
282
|
}
|
|
283
|
+
|
|
284
|
+
// src/openai-stream.ts
|
|
285
|
+
var SUCCESS_FINISH_REASONS = /* @__PURE__ */ new Set(["stop", "tool_calls", "function_call"]);
|
|
140
286
|
async function* parseOpenAIStream(stream) {
|
|
141
287
|
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
288
|
+
let finishReason;
|
|
289
|
+
const terminalError = (message, extra) => {
|
|
290
|
+
const chunk = adapterErrorChunk(message);
|
|
291
|
+
return {
|
|
292
|
+
...chunk,
|
|
293
|
+
metadata: { ...chunk.metadata, ...extra, finishReason }
|
|
294
|
+
};
|
|
295
|
+
};
|
|
296
|
+
const flushToolCalls = function* () {
|
|
297
|
+
for (const [, toolCall] of pendingToolCalls) {
|
|
298
|
+
const parsed = parseCompleteToolArgs(toolCall.args);
|
|
299
|
+
if (!parsed.ok) {
|
|
300
|
+
pendingToolCalls.clear();
|
|
301
|
+
yield {
|
|
302
|
+
type: "error",
|
|
303
|
+
content: parsed.error.message,
|
|
304
|
+
metadata: { error: parsed.error, finishReason }
|
|
305
|
+
};
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
yield {
|
|
309
|
+
type: "tool_call",
|
|
310
|
+
toolCall: { id: toolCall.id, name: toolCall.name, args: parsed.args }
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
pendingToolCalls.clear();
|
|
314
|
+
return true;
|
|
315
|
+
};
|
|
142
316
|
for await (const data of readSSELines(stream)) {
|
|
143
317
|
if (data === "[DONE]") {
|
|
144
|
-
|
|
145
|
-
|
|
318
|
+
if (finishReason !== void 0 && !SUCCESS_FINISH_REASONS.has(finishReason)) {
|
|
319
|
+
pendingToolCalls.clear();
|
|
320
|
+
yield terminalError(
|
|
321
|
+
`OpenAI stream ended with non-success finish reason "${finishReason}".`,
|
|
322
|
+
{ finishReason }
|
|
323
|
+
);
|
|
324
|
+
return;
|
|
146
325
|
}
|
|
147
|
-
|
|
326
|
+
if (!(yield* flushToolCalls())) return;
|
|
148
327
|
yield { type: "done" };
|
|
149
328
|
return;
|
|
150
329
|
}
|
|
151
330
|
try {
|
|
152
331
|
const event = JSON.parse(data);
|
|
153
332
|
const delta = event.choices?.[0]?.delta;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
333
|
+
const candidateFinishReason = event.choices?.find(
|
|
334
|
+
(choice) => typeof choice.finish_reason === "string"
|
|
335
|
+
)?.finish_reason;
|
|
336
|
+
if (typeof candidateFinishReason === "string") finishReason = candidateFinishReason;
|
|
337
|
+
if (typeof delta?.content === "string") yield { type: "text", content: delta.content };
|
|
157
338
|
if (Array.isArray(delta?.tool_calls)) {
|
|
158
339
|
for (const toolCall of delta.tool_calls) {
|
|
159
340
|
const index = toolCall.index ?? 0;
|
|
@@ -182,18 +363,87 @@ async function* parseOpenAIStream(stream) {
|
|
|
182
363
|
} catch {
|
|
183
364
|
}
|
|
184
365
|
}
|
|
185
|
-
|
|
186
|
-
|
|
366
|
+
if (finishReason !== void 0 && !SUCCESS_FINISH_REASONS.has(finishReason)) {
|
|
367
|
+
pendingToolCalls.clear();
|
|
368
|
+
yield terminalError(
|
|
369
|
+
`OpenAI stream ended with non-success finish reason "${finishReason}".`,
|
|
370
|
+
{ finishReason }
|
|
371
|
+
);
|
|
372
|
+
return;
|
|
187
373
|
}
|
|
188
|
-
|
|
374
|
+
if (!finishReason) {
|
|
375
|
+
pendingToolCalls.clear();
|
|
376
|
+
yield terminalError("OpenAI stream ended before a terminal marker.");
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (!(yield* flushToolCalls())) return;
|
|
189
380
|
yield { type: "done" };
|
|
190
381
|
}
|
|
382
|
+
|
|
383
|
+
// src/utils.ts
|
|
384
|
+
function toProviderMessages(messages) {
|
|
385
|
+
const knownToolCallIds = /* @__PURE__ */ new Set();
|
|
386
|
+
const output = [];
|
|
387
|
+
for (const message of messages) {
|
|
388
|
+
if (message.role === "tool") {
|
|
389
|
+
const id = message.toolCallId;
|
|
390
|
+
if (!id || !knownToolCallIds.has(id)) continue;
|
|
391
|
+
output.push({
|
|
392
|
+
role: "tool",
|
|
393
|
+
content: message.content,
|
|
394
|
+
tool_call_id: id
|
|
395
|
+
});
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (message.role === "assistant" && message.toolCalls && message.toolCalls.length > 0) {
|
|
399
|
+
for (const tc of message.toolCalls) knownToolCallIds.add(tc.id);
|
|
400
|
+
output.push({
|
|
401
|
+
role: "assistant",
|
|
402
|
+
content: message.content || null,
|
|
403
|
+
tool_calls: message.toolCalls.map((tc) => ({
|
|
404
|
+
id: tc.id,
|
|
405
|
+
type: "function",
|
|
406
|
+
function: {
|
|
407
|
+
name: tc.name,
|
|
408
|
+
arguments: typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args ?? {})
|
|
409
|
+
}
|
|
410
|
+
}))
|
|
411
|
+
});
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
if (message.role === "assistant" && !message.content) continue;
|
|
415
|
+
output.push({ role: message.role, content: message.content });
|
|
416
|
+
}
|
|
417
|
+
return output;
|
|
418
|
+
}
|
|
191
419
|
async function* parseAnthropicStream(stream) {
|
|
192
420
|
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
421
|
+
let sawMessageStop = false;
|
|
422
|
+
const emitToolCall = function* (tc) {
|
|
423
|
+
const parsed = parseCompleteToolArgs(tc.args);
|
|
424
|
+
if (!parsed.ok) {
|
|
425
|
+
yield {
|
|
426
|
+
type: "error",
|
|
427
|
+
content: parsed.error.message,
|
|
428
|
+
metadata: { error: parsed.error }
|
|
429
|
+
};
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
yield {
|
|
433
|
+
type: "tool_call",
|
|
434
|
+
toolCall: { id: tc.id, name: tc.name, args: parsed.args }
|
|
435
|
+
};
|
|
436
|
+
return true;
|
|
437
|
+
};
|
|
193
438
|
for await (const data of readSSELines(stream)) {
|
|
194
439
|
if (data === "[DONE]") continue;
|
|
195
440
|
try {
|
|
196
441
|
const event = JSON.parse(data);
|
|
442
|
+
if (event.type === "error") {
|
|
443
|
+
const msg = event.error?.message ?? event.error?.type ?? "Anthropic stream error";
|
|
444
|
+
yield adapterErrorChunk(String(msg));
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
197
447
|
if (event.type === "content_block_delta" && event.delta?.type === "text_delta" && event.delta?.text) {
|
|
198
448
|
yield { type: "text", content: event.delta.text };
|
|
199
449
|
} else if (event.type === "content_block_delta" && event.delta?.type === "input_json_delta") {
|
|
@@ -205,22 +455,18 @@ async function* parseAnthropicStream(stream) {
|
|
|
205
455
|
} else if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
|
|
206
456
|
const index = event.index ?? 0;
|
|
207
457
|
pendingToolCalls.set(index, {
|
|
208
|
-
id: event.content_block.id
|
|
209
|
-
name: event.content_block.name,
|
|
458
|
+
id: event.content_block.id ?? `tool-${index}`,
|
|
459
|
+
name: event.content_block.name ?? "unknown",
|
|
210
460
|
args: ""
|
|
211
461
|
});
|
|
212
462
|
} else if (event.type === "content_block_stop") {
|
|
213
463
|
const index = event.index ?? 0;
|
|
214
464
|
const tc = pendingToolCalls.get(index);
|
|
215
465
|
if (tc) {
|
|
216
|
-
yield {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
name: tc.name,
|
|
221
|
-
args: tc.args || "{}"
|
|
222
|
-
}
|
|
223
|
-
};
|
|
466
|
+
if (!(yield* emitToolCall(tc))) {
|
|
467
|
+
pendingToolCalls.clear();
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
224
470
|
pendingToolCalls.delete(index);
|
|
225
471
|
}
|
|
226
472
|
} else if (event.type === "message_delta" && event.usage) {
|
|
@@ -242,8 +488,12 @@ async function* parseAnthropicStream(stream) {
|
|
|
242
488
|
}
|
|
243
489
|
};
|
|
244
490
|
} else if (event.type === "message_stop") {
|
|
491
|
+
sawMessageStop = true;
|
|
245
492
|
for (const [, tc] of pendingToolCalls) {
|
|
246
|
-
yield
|
|
493
|
+
if (!(yield* emitToolCall(tc))) {
|
|
494
|
+
pendingToolCalls.clear();
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
247
497
|
}
|
|
248
498
|
pendingToolCalls.clear();
|
|
249
499
|
yield { type: "done" };
|
|
@@ -252,13 +502,20 @@ async function* parseAnthropicStream(stream) {
|
|
|
252
502
|
} catch {
|
|
253
503
|
}
|
|
254
504
|
}
|
|
255
|
-
for (const [, tc] of pendingToolCalls) {
|
|
256
|
-
yield { type: "tool_call", toolCall: { id: tc.id, name: tc.name, args: tc.args || "{}" } };
|
|
257
|
-
}
|
|
258
505
|
pendingToolCalls.clear();
|
|
506
|
+
if (!sawMessageStop) {
|
|
507
|
+
yield adapterErrorChunk("Anthropic stream ended before message_stop");
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
259
510
|
yield { type: "done" };
|
|
260
511
|
}
|
|
512
|
+
var GEMINI_SUCCESS_FINISH = /* @__PURE__ */ new Set([
|
|
513
|
+
"STOP",
|
|
514
|
+
"stop",
|
|
515
|
+
"FINISH_REASON_STOP"
|
|
516
|
+
]);
|
|
261
517
|
async function* parseGeminiStream(stream) {
|
|
518
|
+
let finishReason;
|
|
262
519
|
for await (const data of readSSELines(stream)) {
|
|
263
520
|
try {
|
|
264
521
|
const event = JSON.parse(data);
|
|
@@ -272,19 +529,33 @@ async function* parseGeminiStream(stream) {
|
|
|
272
529
|
}
|
|
273
530
|
};
|
|
274
531
|
}
|
|
275
|
-
const
|
|
532
|
+
const candidate = event.candidates?.[0];
|
|
533
|
+
if (typeof candidate?.finishReason === "string") {
|
|
534
|
+
finishReason = candidate.finishReason;
|
|
535
|
+
}
|
|
536
|
+
const parts = candidate?.content?.parts;
|
|
276
537
|
if (!Array.isArray(parts)) continue;
|
|
277
538
|
for (const part of parts) {
|
|
278
539
|
if (typeof part.text === "string" && part.text) {
|
|
279
540
|
yield { type: "text", content: part.text };
|
|
280
541
|
} else if (part.functionCall) {
|
|
281
542
|
const fc = part.functionCall;
|
|
543
|
+
const args = typeof fc.args === "string" ? fc.args : JSON.stringify(fc.args ?? {});
|
|
544
|
+
const parsed = parseCompleteToolArgs(args);
|
|
545
|
+
if (!parsed.ok) {
|
|
546
|
+
yield {
|
|
547
|
+
type: "error",
|
|
548
|
+
content: parsed.error.message,
|
|
549
|
+
metadata: { error: parsed.error }
|
|
550
|
+
};
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
282
553
|
yield {
|
|
283
554
|
type: "tool_call",
|
|
284
555
|
toolCall: {
|
|
285
556
|
id: fc.id ?? `${fc.name}-${Date.now()}`,
|
|
286
|
-
name: fc.name,
|
|
287
|
-
args:
|
|
557
|
+
name: fc.name ?? "unknown",
|
|
558
|
+
args: parsed.args
|
|
288
559
|
}
|
|
289
560
|
};
|
|
290
561
|
}
|
|
@@ -292,9 +563,20 @@ async function* parseGeminiStream(stream) {
|
|
|
292
563
|
} catch {
|
|
293
564
|
}
|
|
294
565
|
}
|
|
566
|
+
if (!finishReason || !GEMINI_SUCCESS_FINISH.has(finishReason)) {
|
|
567
|
+
if (finishReason && !GEMINI_SUCCESS_FINISH.has(finishReason)) {
|
|
568
|
+
yield adapterErrorChunk(
|
|
569
|
+
`Gemini stream ended with non-success finish reason "${finishReason}"`
|
|
570
|
+
);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
yield adapterErrorChunk("Gemini stream ended without a successful finishReason");
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
295
576
|
yield { type: "done" };
|
|
296
577
|
}
|
|
297
578
|
async function* parseOllamaStream(stream) {
|
|
579
|
+
let sawDone = false;
|
|
298
580
|
for await (const data of readNDJSONLines(stream)) {
|
|
299
581
|
try {
|
|
300
582
|
const event = JSON.parse(data);
|
|
@@ -304,18 +586,29 @@ async function* parseOllamaStream(stream) {
|
|
|
304
586
|
if (Array.isArray(event.message?.tool_calls)) {
|
|
305
587
|
for (const tc of event.message.tool_calls) {
|
|
306
588
|
if (tc?.function?.name) {
|
|
589
|
+
const args = typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments ?? {});
|
|
590
|
+
const parsed = parseCompleteToolArgs(args);
|
|
591
|
+
if (!parsed.ok) {
|
|
592
|
+
yield {
|
|
593
|
+
type: "error",
|
|
594
|
+
content: parsed.error.message,
|
|
595
|
+
metadata: { error: parsed.error }
|
|
596
|
+
};
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
307
599
|
yield {
|
|
308
600
|
type: "tool_call",
|
|
309
601
|
toolCall: {
|
|
310
602
|
id: tc.id ?? `${tc.function.name}-${Date.now()}`,
|
|
311
603
|
name: tc.function.name,
|
|
312
|
-
args:
|
|
604
|
+
args: parsed.args
|
|
313
605
|
}
|
|
314
606
|
};
|
|
315
607
|
}
|
|
316
608
|
}
|
|
317
609
|
}
|
|
318
610
|
if (event.done) {
|
|
611
|
+
sawDone = true;
|
|
319
612
|
if (typeof event.prompt_eval_count === "number" || typeof event.eval_count === "number") {
|
|
320
613
|
const promptTokens = event.prompt_eval_count ?? 0;
|
|
321
614
|
const completionTokens = event.eval_count ?? 0;
|
|
@@ -334,6 +627,10 @@ async function* parseOllamaStream(stream) {
|
|
|
334
627
|
} catch {
|
|
335
628
|
}
|
|
336
629
|
}
|
|
630
|
+
if (!sawDone) {
|
|
631
|
+
yield adapterErrorChunk("Ollama stream ended without done:true");
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
337
634
|
yield { type: "done" };
|
|
338
635
|
}
|
|
339
636
|
var DEFAULT_RETRY = {
|
|
@@ -345,7 +642,7 @@ var DEFAULT_RETRY = {
|
|
|
345
642
|
if (response) {
|
|
346
643
|
return [408, 429, 500, 502, 503, 504].includes(response.status);
|
|
347
644
|
}
|
|
348
|
-
if (error
|
|
645
|
+
if (isAbortError(error)) return false;
|
|
349
646
|
return true;
|
|
350
647
|
}
|
|
351
648
|
};
|
|
@@ -383,47 +680,23 @@ async function fetchWithRetry(doFetch, signal, retryOpt = {}) {
|
|
|
383
680
|
const retryAfterMs = parseRetryAfter(response.headers.get("retry-after"));
|
|
384
681
|
const delay = retryAfterMs ?? computeDelay(attempt, opts);
|
|
385
682
|
retryOpt.onRetry?.({ attempt, delayMs: delay, reason: `HTTP ${response.status}` });
|
|
386
|
-
await
|
|
387
|
-
|
|
388
|
-
});
|
|
683
|
+
await cancelBody(response.body);
|
|
684
|
+
await abortableSleep(delay, signal, sleep);
|
|
389
685
|
} catch (err) {
|
|
390
686
|
lastError = err;
|
|
391
|
-
if (err
|
|
687
|
+
if (isAbortError(err)) throw err;
|
|
392
688
|
if (attempt >= opts.maxAttempts || !opts.retryOn({ error: err, attempt })) throw err;
|
|
393
689
|
const delay = computeDelay(attempt, opts);
|
|
394
|
-
retryOpt.onRetry?.({
|
|
395
|
-
|
|
690
|
+
retryOpt.onRetry?.({
|
|
691
|
+
attempt,
|
|
692
|
+
delayMs: delay,
|
|
693
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
694
|
+
});
|
|
695
|
+
await abortableSleep(delay, signal, sleep);
|
|
396
696
|
}
|
|
397
697
|
}
|
|
398
698
|
throw lastError ?? new Error("fetchWithRetry: exhausted attempts");
|
|
399
699
|
}
|
|
400
|
-
function createStreamSource(doFetch, parse, errorLabel, retry) {
|
|
401
|
-
let abortController = new AbortController();
|
|
402
|
-
return {
|
|
403
|
-
stream: async function* () {
|
|
404
|
-
const controller = abortController;
|
|
405
|
-
if (!controller) return;
|
|
406
|
-
try {
|
|
407
|
-
const response = await fetchWithRetry(doFetch, controller.signal, retry ?? {});
|
|
408
|
-
if (!response.ok || !response.body) {
|
|
409
|
-
yield { type: "error", content: `${errorLabel} error: ${response.status}` };
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
yield* parse(response.body);
|
|
413
|
-
} catch (err) {
|
|
414
|
-
if (err instanceof DOMException && err.name === "AbortError") return;
|
|
415
|
-
yield {
|
|
416
|
-
type: "error",
|
|
417
|
-
content: err instanceof Error ? err.message : String(err)
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
},
|
|
421
|
-
abort: () => {
|
|
422
|
-
abortController?.abort();
|
|
423
|
-
abortController = null;
|
|
424
|
-
}
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
700
|
function chunkText(text, targetSize = 32) {
|
|
428
701
|
if (text.length <= targetSize) return [text];
|
|
429
702
|
const out = [];
|
|
@@ -449,35 +722,68 @@ function simulateStream(doFetch, extractText, errorLabel, options = {}) {
|
|
|
449
722
|
try {
|
|
450
723
|
const response = await fetchWithRetry(doFetch, controller.signal, retry ?? {});
|
|
451
724
|
if (!response.ok) {
|
|
452
|
-
|
|
725
|
+
await cancelBody(response.body);
|
|
726
|
+
yield adapterErrorChunk(`${errorLabel} error: ${response.status}`);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
let text;
|
|
730
|
+
try {
|
|
731
|
+
text = await extractText(response);
|
|
732
|
+
} catch (err) {
|
|
733
|
+
await cancelBody(response.body);
|
|
734
|
+
if (isAbortError(err)) return;
|
|
735
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
736
|
+
yield adapterErrorChunk(message, { cause: err });
|
|
453
737
|
return;
|
|
454
738
|
}
|
|
455
|
-
const text = await extractText(response);
|
|
456
739
|
const chunks = chunkText(text, chunkSize);
|
|
457
740
|
for (const chunk of chunks) {
|
|
458
|
-
if (abortController === null) return;
|
|
741
|
+
if (abortController === null || controller.signal.aborted) return;
|
|
459
742
|
if (delayMs > 0) {
|
|
460
|
-
await
|
|
461
|
-
const timer = setTimeout(resolve2, delayMs);
|
|
462
|
-
controller.signal.addEventListener(
|
|
463
|
-
"abort",
|
|
464
|
-
() => {
|
|
465
|
-
clearTimeout(timer);
|
|
466
|
-
reject(new DOMException("Aborted", "AbortError"));
|
|
467
|
-
},
|
|
468
|
-
{ once: true }
|
|
469
|
-
);
|
|
470
|
-
});
|
|
743
|
+
await abortableSleep(delayMs, controller.signal, defaultSleep);
|
|
471
744
|
}
|
|
472
745
|
yield { type: "text", content: chunk };
|
|
473
746
|
}
|
|
474
747
|
yield { type: "done" };
|
|
475
748
|
} catch (err) {
|
|
476
|
-
if (err
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
749
|
+
if (isAbortError(err)) return;
|
|
750
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
751
|
+
yield adapterErrorChunk(message, { cause: err });
|
|
752
|
+
}
|
|
753
|
+
},
|
|
754
|
+
abort: () => {
|
|
755
|
+
abortController?.abort();
|
|
756
|
+
abortController = null;
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// src/stream-source.ts
|
|
762
|
+
function createStreamSource(doFetch, parse, errorLabel, retry) {
|
|
763
|
+
let abortController = new AbortController();
|
|
764
|
+
return {
|
|
765
|
+
stream: async function* () {
|
|
766
|
+
const controller = abortController;
|
|
767
|
+
if (!controller) return;
|
|
768
|
+
try {
|
|
769
|
+
const response = await fetchWithRetry(doFetch, controller.signal, retry ?? {});
|
|
770
|
+
if (!response.ok) {
|
|
771
|
+
await cancelBody(response.body);
|
|
772
|
+
yield adapterErrorChunk(`${errorLabel} error: ${response.status}`);
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
if (!response.body) {
|
|
776
|
+
yield adapterErrorChunk(`${errorLabel} error: empty response body`);
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
for await (const chunk of parse(response.body, response)) {
|
|
780
|
+
if (controller.signal.aborted || abortController === null) return;
|
|
781
|
+
yield chunk;
|
|
782
|
+
}
|
|
783
|
+
} catch (err) {
|
|
784
|
+
if (isAbortError(err) || controller.signal.aborted || abortController === null) return;
|
|
785
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
786
|
+
yield adapterErrorChunk(message, { cause: err });
|
|
481
787
|
}
|
|
482
788
|
},
|
|
483
789
|
abort: () => {
|
|
@@ -487,6 +793,139 @@ function simulateStream(doFetch, extractText, errorLabel, options = {}) {
|
|
|
487
793
|
};
|
|
488
794
|
}
|
|
489
795
|
|
|
796
|
+
// src/tool-history.ts
|
|
797
|
+
function toolInput(args) {
|
|
798
|
+
if (args == null) return {};
|
|
799
|
+
if (typeof args === "string") {
|
|
800
|
+
try {
|
|
801
|
+
const parsed = JSON.parse(args);
|
|
802
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
803
|
+
return parsed;
|
|
804
|
+
}
|
|
805
|
+
return { value: parsed };
|
|
806
|
+
} catch {
|
|
807
|
+
return {};
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (typeof args === "object" && !Array.isArray(args)) {
|
|
811
|
+
return args;
|
|
812
|
+
}
|
|
813
|
+
return { value: args };
|
|
814
|
+
}
|
|
815
|
+
function toolResponsePayload(content) {
|
|
816
|
+
try {
|
|
817
|
+
const parsed = JSON.parse(content);
|
|
818
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
819
|
+
return parsed;
|
|
820
|
+
}
|
|
821
|
+
return { result: parsed };
|
|
822
|
+
} catch {
|
|
823
|
+
return { result: content };
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
function toAnthropicMessages(messages) {
|
|
827
|
+
const knownNames = /* @__PURE__ */ new Map();
|
|
828
|
+
const output = [];
|
|
829
|
+
for (const message of messages) {
|
|
830
|
+
if (message.role === "system") continue;
|
|
831
|
+
if (message.role === "assistant" && message.toolCalls && message.toolCalls.length > 0) {
|
|
832
|
+
for (const tc of message.toolCalls) knownNames.set(tc.id, tc.name);
|
|
833
|
+
const blocks = [];
|
|
834
|
+
if (message.content) {
|
|
835
|
+
blocks.push({ type: "text", text: message.content });
|
|
836
|
+
}
|
|
837
|
+
for (const tc of message.toolCalls) {
|
|
838
|
+
blocks.push({
|
|
839
|
+
type: "tool_use",
|
|
840
|
+
id: tc.id,
|
|
841
|
+
name: tc.name,
|
|
842
|
+
input: toolInput(tc.args)
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
output.push({ role: "assistant", content: blocks });
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
if (message.role === "tool") {
|
|
849
|
+
const id = message.toolCallId;
|
|
850
|
+
if (!id || !knownNames.has(id)) continue;
|
|
851
|
+
const block = {
|
|
852
|
+
type: "tool_result",
|
|
853
|
+
tool_use_id: id,
|
|
854
|
+
content: message.content
|
|
855
|
+
};
|
|
856
|
+
const previous2 = output[output.length - 1];
|
|
857
|
+
if (previous2?.role === "user" && Array.isArray(previous2.content) && previous2.content.every((item) => item && typeof item === "object" && item.type === "tool_result")) {
|
|
858
|
+
previous2.content.push(block);
|
|
859
|
+
} else {
|
|
860
|
+
output.push({ role: "user", content: [block] });
|
|
861
|
+
}
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
if (message.role === "assistant" && !message.content) continue;
|
|
865
|
+
const previous = output[output.length - 1];
|
|
866
|
+
if (message.role === "user" && previous?.role === "user") {
|
|
867
|
+
const previousBlocks = Array.isArray(previous.content) ? previous.content : [{ type: "text", text: previous.content }];
|
|
868
|
+
previous.content = [...previousBlocks, { type: "text", text: message.content }];
|
|
869
|
+
} else {
|
|
870
|
+
output.push({ role: message.role, content: message.content });
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
return output;
|
|
874
|
+
}
|
|
875
|
+
function toGeminiContents(messages) {
|
|
876
|
+
const knownNames = /* @__PURE__ */ new Map();
|
|
877
|
+
const output = [];
|
|
878
|
+
for (const message of messages) {
|
|
879
|
+
if (message.role === "system") continue;
|
|
880
|
+
if (message.role === "assistant" && message.toolCalls && message.toolCalls.length > 0) {
|
|
881
|
+
for (const tc of message.toolCalls) knownNames.set(tc.id, tc.name);
|
|
882
|
+
const parts = [];
|
|
883
|
+
if (message.content) {
|
|
884
|
+
parts.push({ text: message.content });
|
|
885
|
+
}
|
|
886
|
+
for (const tc of message.toolCalls) {
|
|
887
|
+
parts.push({
|
|
888
|
+
functionCall: {
|
|
889
|
+
id: tc.id,
|
|
890
|
+
name: tc.name,
|
|
891
|
+
args: toolInput(tc.args)
|
|
892
|
+
}
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
output.push({ role: "model", parts });
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
if (message.role === "tool") {
|
|
899
|
+
const id = message.toolCallId;
|
|
900
|
+
const name = id ? knownNames.get(id) : void 0;
|
|
901
|
+
if (!name) continue;
|
|
902
|
+
const part = {
|
|
903
|
+
functionResponse: {
|
|
904
|
+
id,
|
|
905
|
+
name,
|
|
906
|
+
response: toolResponsePayload(message.content)
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
const previous2 = output[output.length - 1];
|
|
910
|
+
if (previous2?.role === "user" && previous2.parts.every((item) => item.functionResponse !== void 0)) {
|
|
911
|
+
previous2.parts.push(part);
|
|
912
|
+
} else {
|
|
913
|
+
output.push({ role: "user", parts: [part] });
|
|
914
|
+
}
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
if (message.role === "assistant" && !message.content) continue;
|
|
918
|
+
const role = message.role === "assistant" ? "model" : "user";
|
|
919
|
+
const previous = output[output.length - 1];
|
|
920
|
+
if (previous?.role === role) {
|
|
921
|
+
previous.parts.push({ text: message.content });
|
|
922
|
+
} else {
|
|
923
|
+
output.push({ role, parts: [{ text: message.content }] });
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
return output;
|
|
927
|
+
}
|
|
928
|
+
|
|
490
929
|
// src/anthropic.ts
|
|
491
930
|
function anthropic(config) {
|
|
492
931
|
const { apiKey, model, baseUrl = "https://api.anthropic.com", maxTokens = 4096, retry } = config;
|
|
@@ -502,7 +941,7 @@ function anthropic(config) {
|
|
|
502
941
|
const body = {
|
|
503
942
|
model,
|
|
504
943
|
max_tokens: request.context?.maxTokens ?? maxTokens,
|
|
505
|
-
messages: request.messages
|
|
944
|
+
messages: toAnthropicMessages(request.messages),
|
|
506
945
|
system: request.messages.find((message) => message.role === "system")?.content,
|
|
507
946
|
tools: request.context?.tools?.map((tool) => ({
|
|
508
947
|
name: tool.name,
|
|
@@ -600,18 +1039,25 @@ function gemini(config) {
|
|
|
600
1039
|
createSource: (request) => {
|
|
601
1040
|
const systemMessage = request.messages.find((message) => message.role === "system");
|
|
602
1041
|
const body = {
|
|
603
|
-
contents: request.messages
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
1042
|
+
contents: toGeminiContents(request.messages),
|
|
1043
|
+
systemInstruction: systemMessage ? { role: "system", parts: [{ text: systemMessage.content }] } : void 0,
|
|
1044
|
+
tools: request.context?.tools && request.context.tools.length > 0 ? [{
|
|
1045
|
+
functionDeclarations: request.context.tools.map((tool) => ({
|
|
1046
|
+
name: tool.name,
|
|
1047
|
+
description: tool.description,
|
|
1048
|
+
parameters: tool.schema
|
|
1049
|
+
}))
|
|
1050
|
+
}] : void 0
|
|
608
1051
|
};
|
|
609
1052
|
return createStreamSource(
|
|
610
1053
|
(signal) => fetch(
|
|
611
|
-
`${baseUrl}/v1beta/models/${model}:streamGenerateContent?alt=sse
|
|
1054
|
+
`${baseUrl}/v1beta/models/${model}:streamGenerateContent?alt=sse`,
|
|
612
1055
|
{
|
|
613
1056
|
method: "POST",
|
|
614
|
-
headers: {
|
|
1057
|
+
headers: {
|
|
1058
|
+
"Content-Type": "application/json",
|
|
1059
|
+
"x-goog-api-key": apiKey
|
|
1060
|
+
},
|
|
615
1061
|
body: JSON.stringify(body),
|
|
616
1062
|
signal
|
|
617
1063
|
}
|
|
@@ -686,56 +1132,71 @@ function asText(value) {
|
|
|
686
1132
|
}
|
|
687
1133
|
return "";
|
|
688
1134
|
}
|
|
1135
|
+
function isToolStartEvent(eventName) {
|
|
1136
|
+
return eventName === "on_tool_start" || eventName.endsWith("_tool_start");
|
|
1137
|
+
}
|
|
689
1138
|
function langchain(config) {
|
|
690
1139
|
const { runnable, mode = "stream" } = config;
|
|
691
1140
|
return {
|
|
692
|
-
createSource: (request) =>
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
1141
|
+
createSource: (request) => {
|
|
1142
|
+
const controller = new AbortController();
|
|
1143
|
+
return {
|
|
1144
|
+
stream: async function* () {
|
|
1145
|
+
if (controller.signal.aborted) return;
|
|
1146
|
+
try {
|
|
1147
|
+
const runnableConfig = {
|
|
1148
|
+
version: "v2",
|
|
1149
|
+
signal: controller.signal
|
|
1150
|
+
};
|
|
1151
|
+
if (mode === "events" && runnable.streamEvents) {
|
|
1152
|
+
const events = await runnable.streamEvents(
|
|
1153
|
+
{ messages: request.messages },
|
|
1154
|
+
runnableConfig
|
|
1155
|
+
);
|
|
1156
|
+
for await (const event of events) {
|
|
1157
|
+
if (controller.signal.aborted) return;
|
|
1158
|
+
const eventName = String(event.event ?? "");
|
|
1159
|
+
if (eventName.endsWith("_stream")) {
|
|
1160
|
+
const chunk = asText(event.data);
|
|
1161
|
+
if (chunk) yield { type: "text", content: chunk };
|
|
1162
|
+
} else if (isToolStartEvent(eventName) && event.name) {
|
|
1163
|
+
yield {
|
|
1164
|
+
type: "tool_call",
|
|
1165
|
+
toolCall: {
|
|
1166
|
+
id: String(event.run_id ?? `${event.name}-${Date.now()}`),
|
|
1167
|
+
name: String(event.name),
|
|
1168
|
+
args: JSON.stringify(event.data ?? {})
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
} else if (runnable.stream) {
|
|
1174
|
+
const stream = await runnable.stream(
|
|
1175
|
+
{ messages: request.messages },
|
|
1176
|
+
runnableConfig
|
|
1177
|
+
);
|
|
1178
|
+
for await (const value of stream) {
|
|
1179
|
+
if (controller.signal.aborted) return;
|
|
1180
|
+
const chunk = asText(value);
|
|
704
1181
|
if (chunk) yield { type: "text", content: chunk };
|
|
705
|
-
} else if (eventName.endsWith("_start") && event.name) {
|
|
706
|
-
yield {
|
|
707
|
-
type: "tool_call",
|
|
708
|
-
toolCall: {
|
|
709
|
-
id: String(event.run_id ?? `${event.name}-${Date.now()}`),
|
|
710
|
-
name: String(event.name),
|
|
711
|
-
args: JSON.stringify(event.data ?? {})
|
|
712
|
-
}
|
|
713
|
-
};
|
|
714
|
-
} else if (eventName.endsWith("_end")) {
|
|
715
|
-
continue;
|
|
716
1182
|
}
|
|
1183
|
+
} else {
|
|
1184
|
+
yield adapterErrorChunk("Runnable does not implement stream() or streamEvents()");
|
|
1185
|
+
return;
|
|
717
1186
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
}
|
|
724
|
-
} else {
|
|
725
|
-
yield { type: "error", content: "Runnable does not implement stream() or streamEvents()" };
|
|
726
|
-
return;
|
|
1187
|
+
if (controller.signal.aborted) return;
|
|
1188
|
+
yield { type: "done" };
|
|
1189
|
+
} catch (error) {
|
|
1190
|
+
if (isAbortError(error) || controller.signal.aborted) return;
|
|
1191
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1192
|
+
yield adapterErrorChunk(message, { cause: error });
|
|
727
1193
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
type: "error",
|
|
732
|
-
content: error instanceof Error ? error.message : String(error)
|
|
733
|
-
};
|
|
1194
|
+
},
|
|
1195
|
+
abort: () => {
|
|
1196
|
+
controller.abort();
|
|
734
1197
|
}
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
}
|
|
738
|
-
})
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
739
1200
|
};
|
|
740
1201
|
}
|
|
741
1202
|
function langgraph(config) {
|
|
@@ -746,7 +1207,8 @@ function langgraph(config) {
|
|
|
746
1207
|
}
|
|
747
1208
|
|
|
748
1209
|
// src/vercel-ai.ts
|
|
749
|
-
|
|
1210
|
+
var UI_STREAM_HEADER = "x-vercel-ai-ui-message-stream";
|
|
1211
|
+
async function* parseVercelTextStream(stream) {
|
|
750
1212
|
const reader = stream.getReader();
|
|
751
1213
|
const decoder = new TextDecoder();
|
|
752
1214
|
try {
|
|
@@ -761,6 +1223,48 @@ async function* parseVercelStream(stream) {
|
|
|
761
1223
|
}
|
|
762
1224
|
yield { type: "done" };
|
|
763
1225
|
}
|
|
1226
|
+
async function* parseVercelUiMessageStreamV1(stream) {
|
|
1227
|
+
for await (const data of readSSELines(stream)) {
|
|
1228
|
+
if (data === "[DONE]") {
|
|
1229
|
+
yield { type: "done" };
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
try {
|
|
1233
|
+
const part = JSON.parse(data);
|
|
1234
|
+
const type = part.type;
|
|
1235
|
+
if (type === "text-delta") {
|
|
1236
|
+
const delta = part.delta ?? part.textDelta;
|
|
1237
|
+
if (typeof delta === "string" && delta) {
|
|
1238
|
+
yield { type: "text", content: delta };
|
|
1239
|
+
}
|
|
1240
|
+
} else if (type === "reasoning-delta") {
|
|
1241
|
+
const delta = part.delta ?? part.textDelta;
|
|
1242
|
+
if (typeof delta === "string" && delta) {
|
|
1243
|
+
yield { type: "reasoning", content: delta };
|
|
1244
|
+
}
|
|
1245
|
+
} else if (type === "error") {
|
|
1246
|
+
const message = part.errorText ?? part.message ?? "Vercel AI UI stream error";
|
|
1247
|
+
yield adapterErrorChunk(String(message));
|
|
1248
|
+
return;
|
|
1249
|
+
} else if (type === "abort") {
|
|
1250
|
+
yield adapterErrorChunk(part.reason ?? "Vercel AI UI stream aborted by provider");
|
|
1251
|
+
return;
|
|
1252
|
+
} else if (type === "finish") {
|
|
1253
|
+
continue;
|
|
1254
|
+
}
|
|
1255
|
+
} catch {
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
yield adapterErrorChunk("Vercel AI UI stream truncated before [DONE]");
|
|
1259
|
+
}
|
|
1260
|
+
async function* parseVercelStream(stream, response) {
|
|
1261
|
+
const header = response?.headers.get(UI_STREAM_HEADER) ?? response?.headers.get(UI_STREAM_HEADER.toLowerCase());
|
|
1262
|
+
if (header === "v1") {
|
|
1263
|
+
yield* parseVercelUiMessageStreamV1(stream);
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
yield* parseVercelTextStream(stream);
|
|
1267
|
+
}
|
|
764
1268
|
function vercelAI(config) {
|
|
765
1269
|
const { api, headers = {}, retry } = config;
|
|
766
1270
|
return {
|
|
@@ -934,6 +1438,7 @@ async function* parseReplicateStream(stream) {
|
|
|
934
1438
|
const decoder = new TextDecoder();
|
|
935
1439
|
let buffer = "";
|
|
936
1440
|
let currentEvent = "";
|
|
1441
|
+
let sawDone = false;
|
|
937
1442
|
try {
|
|
938
1443
|
while (true) {
|
|
939
1444
|
const { done, value } = await reader.read();
|
|
@@ -951,10 +1456,11 @@ async function* parseReplicateStream(stream) {
|
|
|
951
1456
|
if (currentEvent === "output" && data) {
|
|
952
1457
|
yield { type: "text", content: data };
|
|
953
1458
|
} else if (currentEvent === "done") {
|
|
1459
|
+
sawDone = true;
|
|
954
1460
|
yield { type: "done" };
|
|
955
1461
|
return;
|
|
956
1462
|
} else if (currentEvent === "error") {
|
|
957
|
-
yield
|
|
1463
|
+
yield adapterErrorChunk(data || "replicate stream error");
|
|
958
1464
|
return;
|
|
959
1465
|
}
|
|
960
1466
|
}
|
|
@@ -962,6 +1468,10 @@ async function* parseReplicateStream(stream) {
|
|
|
962
1468
|
} finally {
|
|
963
1469
|
reader.releaseLock();
|
|
964
1470
|
}
|
|
1471
|
+
if (!sawDone) {
|
|
1472
|
+
yield adapterErrorChunk("Replicate stream ended without done event");
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
965
1475
|
yield { type: "done" };
|
|
966
1476
|
}
|
|
967
1477
|
function replicate(config) {
|
|
@@ -994,17 +1504,17 @@ function replicate(config) {
|
|
|
994
1504
|
signal: controller.signal
|
|
995
1505
|
});
|
|
996
1506
|
if (!response.ok) {
|
|
997
|
-
yield
|
|
1507
|
+
yield adapterErrorChunk(`Replicate API error: ${response.status}`);
|
|
998
1508
|
return;
|
|
999
1509
|
}
|
|
1000
1510
|
const prediction = await response.json();
|
|
1001
1511
|
if (prediction.error) {
|
|
1002
|
-
yield
|
|
1512
|
+
yield adapterErrorChunk(prediction.error);
|
|
1003
1513
|
return;
|
|
1004
1514
|
}
|
|
1005
1515
|
const streamUrl = prediction.urls?.stream;
|
|
1006
1516
|
if (!streamUrl) {
|
|
1007
|
-
yield
|
|
1517
|
+
yield adapterErrorChunk("Replicate prediction has no stream URL");
|
|
1008
1518
|
return;
|
|
1009
1519
|
}
|
|
1010
1520
|
const streamResponse = await fetch(streamUrl, {
|
|
@@ -1013,7 +1523,7 @@ function replicate(config) {
|
|
|
1013
1523
|
signal: controller.signal
|
|
1014
1524
|
});
|
|
1015
1525
|
if (!streamResponse.ok || !streamResponse.body) {
|
|
1016
|
-
yield
|
|
1526
|
+
yield adapterErrorChunk(`Replicate stream error: ${streamResponse.status}`);
|
|
1017
1527
|
return;
|
|
1018
1528
|
}
|
|
1019
1529
|
for await (const chunk of parseReplicateStream(streamResponse.body)) {
|
|
@@ -1021,8 +1531,9 @@ function replicate(config) {
|
|
|
1021
1531
|
yield chunk;
|
|
1022
1532
|
}
|
|
1023
1533
|
} catch (err) {
|
|
1024
|
-
if (err
|
|
1025
|
-
|
|
1534
|
+
if (isAbortError(err) || aborted) return;
|
|
1535
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1536
|
+
yield adapterErrorChunk(message, { cause: err });
|
|
1026
1537
|
}
|
|
1027
1538
|
},
|
|
1028
1539
|
abort: () => {
|
|
@@ -1072,10 +1583,7 @@ function vertex(config) {
|
|
|
1072
1583
|
createSource: (request) => {
|
|
1073
1584
|
const systemMessage = request.messages.find((message) => message.role === "system");
|
|
1074
1585
|
const body = {
|
|
1075
|
-
contents: request.messages
|
|
1076
|
-
role: message.role === "assistant" ? "model" : "user",
|
|
1077
|
-
parts: [{ text: message.content }]
|
|
1078
|
-
})),
|
|
1586
|
+
contents: toGeminiContents(request.messages),
|
|
1079
1587
|
systemInstruction: systemMessage ? { role: "system", parts: [{ text: systemMessage.content }] } : void 0,
|
|
1080
1588
|
tools: request.context?.tools && request.context.tools.length > 0 ? [{
|
|
1081
1589
|
functionDeclarations: request.context.tools.map((tool) => ({
|
|
@@ -1131,7 +1639,7 @@ function isAnthropicModel(model) {
|
|
|
1131
1639
|
return model.startsWith("anthropic.");
|
|
1132
1640
|
}
|
|
1133
1641
|
function buildAnthropicBody(request, maxTokens) {
|
|
1134
|
-
const messages = request.messages
|
|
1642
|
+
const messages = toAnthropicMessages(request.messages);
|
|
1135
1643
|
const system = request.messages.find((m) => m.role === "system")?.content;
|
|
1136
1644
|
const tools = request.context?.tools?.map((tool) => ({
|
|
1137
1645
|
name: tool.name,
|
|
@@ -1150,6 +1658,19 @@ function buildAnthropicBody(request, maxTokens) {
|
|
|
1150
1658
|
async function* parseAnthropicBedrockEvents(events) {
|
|
1151
1659
|
const decoder = new TextDecoder();
|
|
1152
1660
|
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
1661
|
+
const emitTool = function* (tc) {
|
|
1662
|
+
const parsed = parseCompleteToolArgs(tc.args);
|
|
1663
|
+
if (!parsed.ok) {
|
|
1664
|
+
yield {
|
|
1665
|
+
type: "error",
|
|
1666
|
+
content: parsed.error.message,
|
|
1667
|
+
metadata: { error: parsed.error }
|
|
1668
|
+
};
|
|
1669
|
+
return false;
|
|
1670
|
+
}
|
|
1671
|
+
yield { type: "tool_call", toolCall: { id: tc.id, name: tc.name, args: parsed.args } };
|
|
1672
|
+
return true;
|
|
1673
|
+
};
|
|
1153
1674
|
for await (const item of events) {
|
|
1154
1675
|
const bytes = item.chunk?.bytes;
|
|
1155
1676
|
if (!bytes) continue;
|
|
@@ -1179,7 +1700,10 @@ async function* parseAnthropicBedrockEvents(events) {
|
|
|
1179
1700
|
const index = event.index ?? 0;
|
|
1180
1701
|
const tc = pendingToolCalls.get(index);
|
|
1181
1702
|
if (tc) {
|
|
1182
|
-
yield
|
|
1703
|
+
if (!(yield* emitTool(tc))) {
|
|
1704
|
+
pendingToolCalls.clear();
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1183
1707
|
pendingToolCalls.delete(index);
|
|
1184
1708
|
}
|
|
1185
1709
|
} else if (type === "message_delta") {
|
|
@@ -1206,18 +1730,18 @@ async function* parseAnthropicBedrockEvents(events) {
|
|
|
1206
1730
|
}
|
|
1207
1731
|
} else if (type === "message_stop") {
|
|
1208
1732
|
for (const [, tc] of pendingToolCalls) {
|
|
1209
|
-
yield
|
|
1733
|
+
if (!(yield* emitTool(tc))) {
|
|
1734
|
+
pendingToolCalls.clear();
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1210
1737
|
}
|
|
1211
1738
|
pendingToolCalls.clear();
|
|
1212
1739
|
yield { type: "done" };
|
|
1213
1740
|
return;
|
|
1214
1741
|
}
|
|
1215
1742
|
}
|
|
1216
|
-
for (const [, tc] of pendingToolCalls) {
|
|
1217
|
-
yield { type: "tool_call", toolCall: { id: tc.id, name: tc.name, args: tc.args || "{}" } };
|
|
1218
|
-
}
|
|
1219
1743
|
pendingToolCalls.clear();
|
|
1220
|
-
yield
|
|
1744
|
+
yield adapterErrorChunk("Bedrock stream ended before message_stop");
|
|
1221
1745
|
}
|
|
1222
1746
|
function bedrock(config) {
|
|
1223
1747
|
const { model, region, maxTokens = 4096 } = config;
|
|
@@ -1252,9 +1776,10 @@ function bedrock(config) {
|
|
|
1252
1776
|
let aborted = false;
|
|
1253
1777
|
return {
|
|
1254
1778
|
stream: async function* () {
|
|
1255
|
-
if (aborted) return;
|
|
1779
|
+
if (aborted || controller.signal.aborted) return;
|
|
1256
1780
|
try {
|
|
1257
1781
|
const client = await getClient();
|
|
1782
|
+
if (aborted || controller.signal.aborted) return;
|
|
1258
1783
|
const sdk = config.client ? null : await loadSdk();
|
|
1259
1784
|
const input = {
|
|
1260
1785
|
modelId: model,
|
|
@@ -1263,17 +1788,19 @@ function bedrock(config) {
|
|
|
1263
1788
|
body: buildAnthropicBody(request, maxTokens)
|
|
1264
1789
|
};
|
|
1265
1790
|
const command = sdk ? new sdk.InvokeModelWithResponseStreamCommand(input) : { input };
|
|
1266
|
-
const response = await client.send(command);
|
|
1791
|
+
const response = await client.send(command, { abortSignal: controller.signal });
|
|
1267
1792
|
if (!response.body) {
|
|
1268
|
-
yield
|
|
1793
|
+
yield adapterErrorChunk("Bedrock: empty response body");
|
|
1269
1794
|
return;
|
|
1270
1795
|
}
|
|
1271
1796
|
for await (const chunk of parseAnthropicBedrockEvents(response.body)) {
|
|
1272
|
-
if (aborted) return;
|
|
1797
|
+
if (aborted || controller.signal.aborted) return;
|
|
1273
1798
|
yield chunk;
|
|
1274
1799
|
}
|
|
1275
1800
|
} catch (err) {
|
|
1276
|
-
|
|
1801
|
+
if (isAbortError(err) || aborted) return;
|
|
1802
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1803
|
+
yield adapterErrorChunk(message, { cause: err });
|
|
1277
1804
|
}
|
|
1278
1805
|
},
|
|
1279
1806
|
abort: () => {
|
|
@@ -1324,26 +1851,62 @@ function webllm(config) {
|
|
|
1324
1851
|
tools: false
|
|
1325
1852
|
},
|
|
1326
1853
|
createSource: (request) => {
|
|
1854
|
+
const controller = new AbortController();
|
|
1327
1855
|
let aborted = false;
|
|
1856
|
+
let activeIter = null;
|
|
1857
|
+
const softAbort = () => {
|
|
1858
|
+
aborted = true;
|
|
1859
|
+
if (!controller.signal.aborted) controller.abort();
|
|
1860
|
+
if (activeIter && typeof activeIter.return === "function") {
|
|
1861
|
+
void activeIter.return(void 0).catch(() => {
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
};
|
|
1328
1865
|
return {
|
|
1329
1866
|
stream: async function* () {
|
|
1330
|
-
if (aborted) return;
|
|
1867
|
+
if (aborted || controller.signal.aborted) return;
|
|
1331
1868
|
try {
|
|
1332
|
-
const engine = await getEngine();
|
|
1869
|
+
const engine = await raceAbort(getEngine(), controller.signal);
|
|
1870
|
+
if (aborted || controller.signal.aborted) return;
|
|
1333
1871
|
const messages = request.messages.map((m) => ({ role: m.role, content: m.content }));
|
|
1334
|
-
const completion = await
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1872
|
+
const completion = await raceAbort(
|
|
1873
|
+
Promise.resolve(
|
|
1874
|
+
engine.chat.completions.create({ messages, stream: true })
|
|
1875
|
+
),
|
|
1876
|
+
controller.signal
|
|
1877
|
+
);
|
|
1878
|
+
if (aborted || controller.signal.aborted) return;
|
|
1879
|
+
activeIter = completion[Symbol.asyncIterator]();
|
|
1880
|
+
while (true) {
|
|
1881
|
+
if (aborted || controller.signal.aborted) {
|
|
1882
|
+
if (typeof activeIter.return === "function") {
|
|
1883
|
+
await activeIter.return(void 0).catch(() => {
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
const next = await raceAbort(activeIter.next(), controller.signal);
|
|
1889
|
+
if (next.done) break;
|
|
1890
|
+
if (aborted || controller.signal.aborted) {
|
|
1891
|
+
if (typeof activeIter.return === "function") {
|
|
1892
|
+
await activeIter.return(void 0).catch(() => {
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1897
|
+
const delta = next.value.choices[0]?.delta?.content;
|
|
1338
1898
|
if (delta) yield { type: "text", content: delta };
|
|
1339
1899
|
}
|
|
1900
|
+
if (aborted || controller.signal.aborted) return;
|
|
1340
1901
|
yield { type: "done" };
|
|
1341
1902
|
} catch (err) {
|
|
1342
|
-
|
|
1903
|
+
if (isAbortError(err) || aborted || controller.signal.aborted) return;
|
|
1904
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1905
|
+
yield adapterErrorChunk(message, { cause: err });
|
|
1343
1906
|
}
|
|
1344
1907
|
},
|
|
1345
1908
|
abort: () => {
|
|
1346
|
-
|
|
1909
|
+
softAbort();
|
|
1347
1910
|
}
|
|
1348
1911
|
};
|
|
1349
1912
|
}
|
|
@@ -1443,22 +2006,57 @@ function createRouter(options) {
|
|
|
1443
2006
|
}
|
|
1444
2007
|
if (typeof policy === "function") {
|
|
1445
2008
|
const maybe = policy({ request, candidates: pool });
|
|
1446
|
-
if (maybe
|
|
2009
|
+
if (typeof maybe !== "string") {
|
|
2010
|
+
const controller = new AbortController();
|
|
2011
|
+
let child;
|
|
1447
2012
|
return {
|
|
1448
2013
|
abort: () => {
|
|
2014
|
+
controller.abort();
|
|
2015
|
+
child?.abort();
|
|
1449
2016
|
},
|
|
1450
2017
|
stream: async function* () {
|
|
1451
|
-
|
|
2018
|
+
let id;
|
|
2019
|
+
try {
|
|
2020
|
+
id = await raceAbort(maybe, controller.signal);
|
|
2021
|
+
} catch (err) {
|
|
2022
|
+
if (isAbortError(err)) return;
|
|
2023
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
2024
|
+
yield {
|
|
2025
|
+
type: "error",
|
|
2026
|
+
content: error.message,
|
|
2027
|
+
metadata: { error }
|
|
2028
|
+
};
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
1452
2031
|
const c3 = pool.find((x) => x.id === id);
|
|
1453
2032
|
if (!c3) {
|
|
1454
|
-
|
|
2033
|
+
const error = new core.ConfigError({
|
|
1455
2034
|
code: core.ErrorCodes.AK_CONFIG_INVALID,
|
|
1456
2035
|
message: `policy returned unknown id: ${id}`,
|
|
1457
2036
|
hint: "Custom policy functions must return one of the candidate ids."
|
|
1458
2037
|
});
|
|
2038
|
+
yield {
|
|
2039
|
+
type: "error",
|
|
2040
|
+
content: error.message,
|
|
2041
|
+
metadata: { error }
|
|
2042
|
+
};
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
try {
|
|
2046
|
+
options.onRoute?.({ id: c3.id, reason: pickedBy ?? "custom policy", request });
|
|
2047
|
+
child = c3.adapter.createSource(request);
|
|
2048
|
+
for await (const chunk of child.stream()) {
|
|
2049
|
+
yield chunk;
|
|
2050
|
+
}
|
|
2051
|
+
} catch (err) {
|
|
2052
|
+
if (controller.signal.aborted || isAbortError(err)) return;
|
|
2053
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
2054
|
+
yield {
|
|
2055
|
+
type: "error",
|
|
2056
|
+
content: error.message,
|
|
2057
|
+
metadata: { error }
|
|
2058
|
+
};
|
|
1459
2059
|
}
|
|
1460
|
-
options.onRoute?.({ id: c3.id, reason: pickedBy ?? "custom policy", request });
|
|
1461
|
-
for await (const chunk of c3.adapter.createSource(request).stream()) yield chunk;
|
|
1462
2060
|
}
|
|
1463
2061
|
};
|
|
1464
2062
|
}
|
|
@@ -1546,9 +2144,17 @@ async function drain(source, timeoutMs) {
|
|
|
1546
2144
|
for await (const chunk of source.stream()) {
|
|
1547
2145
|
chunks.push(chunk);
|
|
1548
2146
|
if (chunk.type === "text" && typeof chunk.content === "string") text += chunk.content;
|
|
2147
|
+
if (chunk.type === "error") {
|
|
2148
|
+
const metaErr = chunk.metadata?.error;
|
|
2149
|
+
error = metaErr instanceof Error ? metaErr : new Error(chunk.content ?? "stream error");
|
|
2150
|
+
}
|
|
1549
2151
|
}
|
|
1550
2152
|
} catch (err) {
|
|
1551
|
-
|
|
2153
|
+
if (!isAbortError(err)) {
|
|
2154
|
+
error = err instanceof Error ? err : new Error(String(err));
|
|
2155
|
+
} else if (!timedOut) {
|
|
2156
|
+
error = err instanceof Error ? err : new Error(String(err));
|
|
2157
|
+
}
|
|
1552
2158
|
} finally {
|
|
1553
2159
|
if (timer) clearTimeout(timer);
|
|
1554
2160
|
}
|
|
@@ -1587,10 +2193,19 @@ function createEnsembleAdapter(options) {
|
|
|
1587
2193
|
const sources = [];
|
|
1588
2194
|
const run = async () => {
|
|
1589
2195
|
const tasks = options.candidates.map(async (c, i) => {
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
2196
|
+
try {
|
|
2197
|
+
const source = c.adapter.createSource(request);
|
|
2198
|
+
sources[i] = source;
|
|
2199
|
+
if (aborted) {
|
|
2200
|
+
source.abort();
|
|
2201
|
+
return { id: c.id, chunks: [], text: "", error: new Error("aborted") };
|
|
2202
|
+
}
|
|
2203
|
+
const { chunks, text, error } = await drain(source, options.timeoutMs);
|
|
2204
|
+
return { id: c.id, chunks, text, error };
|
|
2205
|
+
} catch (err) {
|
|
2206
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
2207
|
+
return { id: c.id, chunks: [], text: "", error };
|
|
2208
|
+
}
|
|
1594
2209
|
});
|
|
1595
2210
|
return Promise.all(tasks);
|
|
1596
2211
|
};
|
|
@@ -1613,22 +2228,51 @@ function createEnsembleAdapter(options) {
|
|
|
1613
2228
|
for (const s of sources) s?.abort();
|
|
1614
2229
|
},
|
|
1615
2230
|
stream: async function* () {
|
|
2231
|
+
if (aborted) return;
|
|
1616
2232
|
const branches = await run();
|
|
1617
2233
|
options.onBranches?.(branches);
|
|
1618
|
-
if (aborted)
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
2234
|
+
if (aborted) {
|
|
2235
|
+
for (const s of sources) s?.abort();
|
|
2236
|
+
return;
|
|
2237
|
+
}
|
|
2238
|
+
try {
|
|
2239
|
+
const text = await finalText(branches);
|
|
2240
|
+
if (!text && branches.every((b) => b.error)) {
|
|
2241
|
+
const summary = branches.map((b) => `${b.id}: ${b.error?.message ?? "unknown"}`).join("; ");
|
|
2242
|
+
const err = new core.AdapterError({
|
|
2243
|
+
code: core.ErrorCodes.AK_ADAPTER_STREAM_FAILED,
|
|
2244
|
+
message: `all ensemble branches failed (${summary})`,
|
|
2245
|
+
hint: "Inspect each branch error via options.onBranches or the err.branchErrors field."
|
|
2246
|
+
});
|
|
2247
|
+
err.branchErrors = branches.map((b) => ({ id: b.id, error: b.error }));
|
|
2248
|
+
yield {
|
|
2249
|
+
type: "error",
|
|
2250
|
+
content: err.message,
|
|
2251
|
+
metadata: { error: err }
|
|
2252
|
+
};
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
yield {
|
|
2256
|
+
type: "text",
|
|
2257
|
+
content: text,
|
|
2258
|
+
metadata: {
|
|
2259
|
+
ensemble: branches.map((b) => ({
|
|
2260
|
+
id: b.id,
|
|
2261
|
+
bytes: b.text.length,
|
|
2262
|
+
error: b.error?.message
|
|
2263
|
+
}))
|
|
2264
|
+
}
|
|
2265
|
+
};
|
|
2266
|
+
yield { type: "done" };
|
|
2267
|
+
} catch (err) {
|
|
2268
|
+
if (aborted || isAbortError(err)) return;
|
|
2269
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
2270
|
+
yield {
|
|
2271
|
+
type: "error",
|
|
2272
|
+
content: error.message,
|
|
2273
|
+
metadata: { error }
|
|
2274
|
+
};
|
|
1629
2275
|
}
|
|
1630
|
-
yield { type: "text", content: text, metadata: { ensemble: branches.map((b) => ({ id: b.id, bytes: b.text.length, error: b.error?.message })) } };
|
|
1631
|
-
yield { type: "done" };
|
|
1632
2276
|
}
|
|
1633
2277
|
};
|
|
1634
2278
|
}
|
|
@@ -1659,57 +2303,119 @@ function createFallbackAdapter(candidates, options = {}) {
|
|
|
1659
2303
|
let source;
|
|
1660
2304
|
try {
|
|
1661
2305
|
source = candidate.adapter.createSource(request);
|
|
1662
|
-
} catch (
|
|
1663
|
-
const error =
|
|
2306
|
+
} catch (err2) {
|
|
2307
|
+
const error = err2 instanceof Error ? err2 : new Error(String(err2));
|
|
1664
2308
|
errors.push({ id: candidate.id, error });
|
|
1665
2309
|
options.onFallback?.({ id: candidate.id, index: i, error });
|
|
1666
|
-
if (options.shouldRetry && !options.shouldRetry(error, i))
|
|
2310
|
+
if (options.shouldRetry && !options.shouldRetry(error, i)) {
|
|
2311
|
+
yield {
|
|
2312
|
+
type: "error",
|
|
2313
|
+
content: error.message,
|
|
2314
|
+
metadata: { error }
|
|
2315
|
+
};
|
|
2316
|
+
return;
|
|
2317
|
+
}
|
|
1667
2318
|
continue;
|
|
1668
2319
|
}
|
|
1669
2320
|
active = source;
|
|
1670
2321
|
const iter = source.stream()[Symbol.asyncIterator]();
|
|
1671
2322
|
let firstReal;
|
|
1672
2323
|
let branchError;
|
|
2324
|
+
const leadingUsage = [];
|
|
1673
2325
|
try {
|
|
1674
2326
|
while (true) {
|
|
2327
|
+
if (aborted) {
|
|
2328
|
+
source.abort();
|
|
2329
|
+
return;
|
|
2330
|
+
}
|
|
1675
2331
|
const r = await iter.next();
|
|
1676
2332
|
if (r.done) {
|
|
1677
2333
|
branchError = new Error(`candidate ${candidate.id} emitted no chunks`);
|
|
1678
2334
|
break;
|
|
1679
2335
|
}
|
|
1680
2336
|
if (r.value.type === "error") {
|
|
1681
|
-
|
|
2337
|
+
const metaErr = r.value.metadata?.error;
|
|
2338
|
+
branchError = metaErr instanceof Error ? metaErr : new Error(
|
|
1682
2339
|
`candidate ${candidate.id}: ${r.value.content ?? "stream error"}`
|
|
1683
2340
|
);
|
|
1684
2341
|
break;
|
|
1685
2342
|
}
|
|
2343
|
+
if (r.value.type === "usage") {
|
|
2344
|
+
leadingUsage.push(r.value);
|
|
2345
|
+
continue;
|
|
2346
|
+
}
|
|
1686
2347
|
firstReal = r.value;
|
|
1687
2348
|
break;
|
|
1688
2349
|
}
|
|
1689
|
-
} catch (
|
|
1690
|
-
|
|
2350
|
+
} catch (err2) {
|
|
2351
|
+
if (isAbortError(err2) || aborted) {
|
|
2352
|
+
source.abort();
|
|
2353
|
+
return;
|
|
2354
|
+
}
|
|
2355
|
+
branchError = err2 instanceof Error ? err2 : new Error(String(err2));
|
|
1691
2356
|
}
|
|
1692
2357
|
if (branchError || !firstReal) {
|
|
1693
2358
|
const error = branchError ?? new Error(`candidate ${candidate.id} emitted no chunks`);
|
|
1694
2359
|
errors.push({ id: candidate.id, error });
|
|
1695
2360
|
options.onFallback?.({ id: candidate.id, index: i, error });
|
|
1696
|
-
|
|
2361
|
+
try {
|
|
2362
|
+
source.abort();
|
|
2363
|
+
} catch {
|
|
2364
|
+
}
|
|
2365
|
+
if (typeof iter.return === "function") {
|
|
2366
|
+
try {
|
|
2367
|
+
await iter.return(void 0);
|
|
2368
|
+
} catch {
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
if (options.shouldRetry && !options.shouldRetry(error, i)) {
|
|
2372
|
+
yield {
|
|
2373
|
+
type: "error",
|
|
2374
|
+
content: error.message,
|
|
2375
|
+
metadata: { error }
|
|
2376
|
+
};
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
1697
2379
|
active = void 0;
|
|
1698
2380
|
continue;
|
|
1699
2381
|
}
|
|
2382
|
+
for (const chunk of leadingUsage) yield chunk;
|
|
1700
2383
|
yield firstReal;
|
|
1701
2384
|
while (true) {
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
2385
|
+
if (aborted) {
|
|
2386
|
+
source.abort();
|
|
2387
|
+
return;
|
|
2388
|
+
}
|
|
2389
|
+
try {
|
|
2390
|
+
const next = await iter.next();
|
|
2391
|
+
if (next.done) return;
|
|
2392
|
+
yield next.value;
|
|
2393
|
+
} catch (err2) {
|
|
2394
|
+
if (isAbortError(err2) || aborted) {
|
|
2395
|
+
source.abort();
|
|
2396
|
+
return;
|
|
2397
|
+
}
|
|
2398
|
+
const error = err2 instanceof Error ? err2 : new Error(String(err2));
|
|
2399
|
+
yield {
|
|
2400
|
+
type: "error",
|
|
2401
|
+
content: error.message,
|
|
2402
|
+
metadata: { error }
|
|
2403
|
+
};
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
1705
2406
|
}
|
|
1706
2407
|
}
|
|
1707
2408
|
const summary = errors.map((e) => `${e.id}: ${e.error.message}`).join("; ");
|
|
1708
|
-
|
|
2409
|
+
const err = new core.AdapterError({
|
|
1709
2410
|
code: core.ErrorCodes.AK_ADAPTER_STREAM_FAILED,
|
|
1710
2411
|
message: `all fallback candidates failed (${summary})`,
|
|
1711
2412
|
hint: "Check each candidate adapter's configuration and provider keys."
|
|
1712
2413
|
});
|
|
2414
|
+
yield {
|
|
2415
|
+
type: "error",
|
|
2416
|
+
content: err.message,
|
|
2417
|
+
metadata: { error: err }
|
|
2418
|
+
};
|
|
1713
2419
|
}
|
|
1714
2420
|
};
|
|
1715
2421
|
}
|
|
@@ -1727,25 +2433,48 @@ function mockAdapter(options) {
|
|
|
1727
2433
|
usage: true
|
|
1728
2434
|
},
|
|
1729
2435
|
createSource: (request) => {
|
|
1730
|
-
|
|
1731
|
-
|
|
2436
|
+
const controller = new AbortController();
|
|
2437
|
+
let myCall;
|
|
1732
2438
|
let cancelled = false;
|
|
1733
2439
|
return {
|
|
1734
2440
|
stream: async function* () {
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
2441
|
+
if (cancelled || controller.signal.aborted) return;
|
|
2442
|
+
try {
|
|
2443
|
+
if (myCall === void 0) {
|
|
2444
|
+
myCall = callIndex++;
|
|
2445
|
+
history?.push(request);
|
|
2446
|
+
}
|
|
2447
|
+
const chunks = resolve(response, myCall, request);
|
|
2448
|
+
for (const chunk of chunks) {
|
|
2449
|
+
if (cancelled || controller.signal.aborted) return;
|
|
2450
|
+
if (delayMs > 0) {
|
|
2451
|
+
await abortableSleep(delayMs, controller.signal, (ms) => new Promise((resolveTimer) => setTimeout(resolveTimer, ms)));
|
|
2452
|
+
}
|
|
2453
|
+
if (cancelled || controller.signal.aborted) return;
|
|
2454
|
+
if (chunk.type === "error" && !(chunk.metadata?.error instanceof Error)) {
|
|
2455
|
+
const normalized = adapterErrorChunk(
|
|
2456
|
+
chunk.content ?? "Mock adapter error",
|
|
2457
|
+
{ cause: chunk.metadata?.error }
|
|
2458
|
+
);
|
|
2459
|
+
yield {
|
|
2460
|
+
...chunk,
|
|
2461
|
+
metadata: { ...chunk.metadata, ...normalized.metadata }
|
|
2462
|
+
};
|
|
2463
|
+
return;
|
|
2464
|
+
}
|
|
2465
|
+
yield chunk;
|
|
2466
|
+
if (chunk.type === "done" || chunk.type === "error") return;
|
|
1743
2467
|
}
|
|
2468
|
+
yield { type: "done" };
|
|
2469
|
+
} catch (error) {
|
|
2470
|
+
if (cancelled || controller.signal.aborted || isAbortError(error)) return;
|
|
2471
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2472
|
+
yield adapterErrorChunk(message, { cause: error });
|
|
1744
2473
|
}
|
|
1745
|
-
if (!endedExplicitly) yield { type: "done" };
|
|
1746
2474
|
},
|
|
1747
2475
|
abort: () => {
|
|
1748
2476
|
cancelled = true;
|
|
2477
|
+
controller.abort();
|
|
1749
2478
|
}
|
|
1750
2479
|
};
|
|
1751
2480
|
}
|
|
@@ -1768,22 +2497,43 @@ function resolve(response, callIndex, request) {
|
|
|
1768
2497
|
}
|
|
1769
2498
|
function recordingAdapter(inner, sink) {
|
|
1770
2499
|
return {
|
|
2500
|
+
capabilities: inner.capabilities,
|
|
1771
2501
|
createSource: (request) => {
|
|
1772
|
-
|
|
2502
|
+
let innerSource;
|
|
1773
2503
|
const captured = [];
|
|
1774
|
-
|
|
2504
|
+
let aborted = false;
|
|
1775
2505
|
return {
|
|
1776
2506
|
stream: async function* () {
|
|
2507
|
+
if (aborted) return;
|
|
2508
|
+
const recordedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1777
2509
|
try {
|
|
2510
|
+
innerSource = inner.createSource(request);
|
|
1778
2511
|
for await (const chunk of innerSource.stream()) {
|
|
2512
|
+
if (aborted) return;
|
|
1779
2513
|
captured.push(chunk);
|
|
1780
2514
|
yield chunk;
|
|
2515
|
+
if (chunk.type === "done" || chunk.type === "error") return;
|
|
1781
2516
|
}
|
|
2517
|
+
const errorChunk = adapterErrorChunk("Recorded adapter ended without a terminal chunk");
|
|
2518
|
+
captured.push(errorChunk);
|
|
2519
|
+
yield errorChunk;
|
|
2520
|
+
} catch (error) {
|
|
2521
|
+
if (aborted || isAbortError(error)) return;
|
|
2522
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2523
|
+
const errorChunk = adapterErrorChunk(message, { cause: error });
|
|
2524
|
+
captured.push(errorChunk);
|
|
2525
|
+
yield errorChunk;
|
|
1782
2526
|
} finally {
|
|
1783
|
-
|
|
2527
|
+
try {
|
|
2528
|
+
await sink.push({ recordedAt, request, chunks: captured });
|
|
2529
|
+
} catch {
|
|
2530
|
+
}
|
|
1784
2531
|
}
|
|
1785
2532
|
},
|
|
1786
|
-
abort: () =>
|
|
2533
|
+
abort: () => {
|
|
2534
|
+
aborted = true;
|
|
2535
|
+
innerSource?.abort();
|
|
2536
|
+
}
|
|
1787
2537
|
};
|
|
1788
2538
|
}
|
|
1789
2539
|
};
|
|
@@ -1809,15 +2559,40 @@ function replayAdapter(fixture) {
|
|
|
1809
2559
|
response: fixture.map((turn) => turn.chunks)
|
|
1810
2560
|
});
|
|
1811
2561
|
}
|
|
1812
|
-
|
|
1813
|
-
// src/embedders/shared.ts
|
|
1814
|
-
var ERROR_BODY_PREVIEW_CHARS = 200;
|
|
1815
|
-
async function throwIfNotOk(response, label, url) {
|
|
2562
|
+
async function throwIfNotOk(response, label, _url) {
|
|
1816
2563
|
if (response.ok) return;
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
2564
|
+
void response.body?.cancel().catch(() => {
|
|
2565
|
+
});
|
|
2566
|
+
throw embeddingError(label, `HTTP ${response.status}`);
|
|
2567
|
+
}
|
|
2568
|
+
function embeddingError(label, detail, cause) {
|
|
2569
|
+
return new core.AdapterError({
|
|
2570
|
+
code: core.ErrorCodes.AK_ADAPTER_STREAM_FAILED,
|
|
2571
|
+
message: `${label} embedding failed: ${detail}`,
|
|
2572
|
+
cause
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2575
|
+
function requireEmbeddingVector(value, label) {
|
|
2576
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
2577
|
+
throw new core.AdapterError({
|
|
2578
|
+
code: core.ErrorCodes.AK_ADAPTER_STREAM_FAILED,
|
|
2579
|
+
message: `${label} returned an empty or missing embedding vector`,
|
|
2580
|
+
hint: "The provider responded 2xx but the embedding payload was empty or malformed."
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2583
|
+
const vector = [];
|
|
2584
|
+
for (let i = 0; i < value.length; i++) {
|
|
2585
|
+
const n = value[i];
|
|
2586
|
+
if (typeof n !== "number" || !Number.isFinite(n)) {
|
|
2587
|
+
throw new core.AdapterError({
|
|
2588
|
+
code: core.ErrorCodes.AK_ADAPTER_STREAM_FAILED,
|
|
2589
|
+
message: `${label} returned a non-finite embedding value at index ${i}`,
|
|
2590
|
+
hint: "Embedding vectors must be finite numbers only."
|
|
2591
|
+
});
|
|
2592
|
+
}
|
|
2593
|
+
vector.push(n);
|
|
2594
|
+
}
|
|
2595
|
+
return vector;
|
|
1821
2596
|
}
|
|
1822
2597
|
|
|
1823
2598
|
// src/embedders/openai.ts
|
|
@@ -1826,7 +2601,7 @@ async function fetchAvailableModels(baseUrl, apiKey) {
|
|
|
1826
2601
|
const response = await fetch(url, {
|
|
1827
2602
|
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
1828
2603
|
});
|
|
1829
|
-
await throwIfNotOk(response, "openai"
|
|
2604
|
+
await throwIfNotOk(response, "openai");
|
|
1830
2605
|
const data = await response.json();
|
|
1831
2606
|
return data.data.map((m) => m.id).filter((id) => id.includes("embed")).sort();
|
|
1832
2607
|
}
|
|
@@ -1834,13 +2609,9 @@ async function buildModelError(baseUrl, apiKey, originalError) {
|
|
|
1834
2609
|
try {
|
|
1835
2610
|
const models = await fetchAvailableModels(baseUrl, apiKey);
|
|
1836
2611
|
const list = models.length > 0 ? models.join(", ") : "none found";
|
|
1837
|
-
return
|
|
1838
|
-
`OpenAI embedding failed: ${originalError}. Available embedding models: ${list}`
|
|
1839
|
-
);
|
|
2612
|
+
return embeddingError("OpenAI", `${originalError}. Available embedding models: ${list}`);
|
|
1840
2613
|
} catch (fetchError) {
|
|
1841
|
-
return
|
|
1842
|
-
`OpenAI embedding failed: ${originalError}. Could not fetch available models: ${fetchError.message}`
|
|
1843
|
-
);
|
|
2614
|
+
return embeddingError("OpenAI", `${originalError}. Could not fetch available models`, fetchError);
|
|
1844
2615
|
}
|
|
1845
2616
|
}
|
|
1846
2617
|
function openaiEmbedder(config) {
|
|
@@ -1855,20 +2626,24 @@ function openaiEmbedder(config) {
|
|
|
1855
2626
|
body: JSON.stringify({ model, input: text })
|
|
1856
2627
|
});
|
|
1857
2628
|
if (!response.ok) {
|
|
1858
|
-
|
|
1859
|
-
|
|
2629
|
+
void response.body?.cancel().catch(() => {
|
|
2630
|
+
});
|
|
2631
|
+
const message = `HTTP ${response.status}`;
|
|
1860
2632
|
throw await buildModelError(baseUrl, apiKey, message);
|
|
1861
2633
|
}
|
|
1862
2634
|
const data = await response.json();
|
|
1863
|
-
|
|
2635
|
+
const embedding = data.data?.[0]?.embedding;
|
|
2636
|
+
return requireEmbeddingVector(embedding, "OpenAI");
|
|
1864
2637
|
};
|
|
1865
2638
|
}
|
|
1866
2639
|
|
|
1867
2640
|
// src/embedders/gemini.ts
|
|
1868
2641
|
async function fetchAvailableModels2(baseUrl, apiKey) {
|
|
1869
|
-
const url = `${baseUrl}/v1beta/models
|
|
1870
|
-
const response = await fetch(url
|
|
1871
|
-
|
|
2642
|
+
const url = `${baseUrl}/v1beta/models`;
|
|
2643
|
+
const response = await fetch(url, {
|
|
2644
|
+
headers: { "x-goog-api-key": apiKey }
|
|
2645
|
+
});
|
|
2646
|
+
await throwIfNotOk(response, "gemini");
|
|
1872
2647
|
const data = await response.json();
|
|
1873
2648
|
return data.models.filter((m) => m.supportedGenerationMethods.includes("embedContent")).map((m) => m.name.replace("models/", "")).sort();
|
|
1874
2649
|
}
|
|
@@ -1876,13 +2651,9 @@ async function buildModelError2(baseUrl, apiKey, originalError) {
|
|
|
1876
2651
|
try {
|
|
1877
2652
|
const models = await fetchAvailableModels2(baseUrl, apiKey);
|
|
1878
2653
|
const list = models.length > 0 ? models.join(", ") : "none found";
|
|
1879
|
-
return
|
|
1880
|
-
`Gemini embedding failed: ${originalError}. Available embedding models: ${list}`
|
|
1881
|
-
);
|
|
2654
|
+
return embeddingError("Gemini", `${originalError}. Available embedding models: ${list}`);
|
|
1882
2655
|
} catch (fetchError) {
|
|
1883
|
-
return
|
|
1884
|
-
`Gemini embedding failed: ${originalError}. Could not fetch available models: ${fetchError.message}`
|
|
1885
|
-
);
|
|
2656
|
+
return embeddingError("Gemini", `${originalError}. Could not fetch available models`, fetchError);
|
|
1886
2657
|
}
|
|
1887
2658
|
}
|
|
1888
2659
|
function geminiEmbedder(config) {
|
|
@@ -1893,10 +2664,13 @@ function geminiEmbedder(config) {
|
|
|
1893
2664
|
} = config;
|
|
1894
2665
|
return async (text) => {
|
|
1895
2666
|
const response = await fetch(
|
|
1896
|
-
`${baseUrl}/v1beta/models/${model}:embedContent
|
|
2667
|
+
`${baseUrl}/v1beta/models/${model}:embedContent`,
|
|
1897
2668
|
{
|
|
1898
2669
|
method: "POST",
|
|
1899
|
-
headers: {
|
|
2670
|
+
headers: {
|
|
2671
|
+
"Content-Type": "application/json",
|
|
2672
|
+
"x-goog-api-key": apiKey
|
|
2673
|
+
},
|
|
1900
2674
|
body: JSON.stringify({
|
|
1901
2675
|
model: `models/${model}`,
|
|
1902
2676
|
content: { parts: [{ text }] }
|
|
@@ -1904,12 +2678,13 @@ function geminiEmbedder(config) {
|
|
|
1904
2678
|
}
|
|
1905
2679
|
);
|
|
1906
2680
|
if (!response.ok) {
|
|
1907
|
-
|
|
1908
|
-
|
|
2681
|
+
void response.body?.cancel().catch(() => {
|
|
2682
|
+
});
|
|
2683
|
+
const message = `HTTP ${response.status}`;
|
|
1909
2684
|
throw await buildModelError2(baseUrl, apiKey, message);
|
|
1910
2685
|
}
|
|
1911
2686
|
const data = await response.json();
|
|
1912
|
-
return data.embedding
|
|
2687
|
+
return requireEmbeddingVector(data.embedding?.values, "Gemini");
|
|
1913
2688
|
};
|
|
1914
2689
|
}
|
|
1915
2690
|
|
|
@@ -1917,7 +2692,7 @@ function geminiEmbedder(config) {
|
|
|
1917
2692
|
async function fetchAvailableModels3(baseUrl) {
|
|
1918
2693
|
const url = `${baseUrl}/api/tags`;
|
|
1919
2694
|
const response = await fetch(url);
|
|
1920
|
-
await throwIfNotOk(response, "ollama"
|
|
2695
|
+
await throwIfNotOk(response, "ollama");
|
|
1921
2696
|
const data = await response.json();
|
|
1922
2697
|
return data.models.map((m) => m.name).filter((name) => name.includes("embed")).sort();
|
|
1923
2698
|
}
|
|
@@ -1925,13 +2700,9 @@ async function buildModelError3(baseUrl, originalError) {
|
|
|
1925
2700
|
try {
|
|
1926
2701
|
const models = await fetchAvailableModels3(baseUrl);
|
|
1927
2702
|
const list = models.length > 0 ? models.join(", ") : "none found";
|
|
1928
|
-
return
|
|
1929
|
-
`Ollama embedding failed: ${originalError}. Available embedding models: ${list}`
|
|
1930
|
-
);
|
|
2703
|
+
return embeddingError("Ollama", `${originalError}. Available embedding models: ${list}`);
|
|
1931
2704
|
} catch (fetchError) {
|
|
1932
|
-
return
|
|
1933
|
-
`Ollama embedding failed: ${originalError}. Could not fetch available models: ${fetchError.message}`
|
|
1934
|
-
);
|
|
2705
|
+
return embeddingError("Ollama", `${originalError}. Could not fetch available models`, fetchError);
|
|
1935
2706
|
}
|
|
1936
2707
|
}
|
|
1937
2708
|
function ollamaEmbedder(config) {
|
|
@@ -1943,12 +2714,14 @@ function ollamaEmbedder(config) {
|
|
|
1943
2714
|
body: JSON.stringify({ model, input: text })
|
|
1944
2715
|
});
|
|
1945
2716
|
if (!response.ok) {
|
|
1946
|
-
|
|
1947
|
-
|
|
2717
|
+
void response.body?.cancel().catch(() => {
|
|
2718
|
+
});
|
|
2719
|
+
const message = `HTTP ${response.status}`;
|
|
1948
2720
|
throw await buildModelError3(baseUrl, message);
|
|
1949
2721
|
}
|
|
1950
2722
|
const data = await response.json();
|
|
1951
|
-
|
|
2723
|
+
const first = Array.isArray(data.embeddings) ? data.embeddings[0] : void 0;
|
|
2724
|
+
return requireEmbeddingVector(first, "Ollama");
|
|
1952
2725
|
};
|
|
1953
2726
|
}
|
|
1954
2727
|
async function fetchAvailableModels4(provider, baseUrl, apiKey) {
|
|
@@ -1956,7 +2729,7 @@ async function fetchAvailableModels4(provider, baseUrl, apiKey) {
|
|
|
1956
2729
|
const response = await fetch(url, {
|
|
1957
2730
|
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
1958
2731
|
});
|
|
1959
|
-
await throwIfNotOk(response, provider
|
|
2732
|
+
await throwIfNotOk(response, provider);
|
|
1960
2733
|
const data = await response.json();
|
|
1961
2734
|
return data.data.map((m) => m.id).filter((id) => id.includes("embed")).sort();
|
|
1962
2735
|
}
|
|
@@ -1964,13 +2737,9 @@ async function buildModelError4(provider, baseUrl, apiKey, originalError) {
|
|
|
1964
2737
|
try {
|
|
1965
2738
|
const models = await fetchAvailableModels4(provider, baseUrl, apiKey);
|
|
1966
2739
|
const list = models.length > 0 ? models.join(", ") : "none found";
|
|
1967
|
-
return
|
|
1968
|
-
`${provider} embedding failed: ${originalError}. Available embedding models: ${list}`
|
|
1969
|
-
);
|
|
2740
|
+
return embeddingError(provider, `${originalError}. Available embedding models: ${list}`);
|
|
1970
2741
|
} catch (fetchError) {
|
|
1971
|
-
return
|
|
1972
|
-
`${provider} embedding failed: ${originalError}. Could not fetch available models: ${fetchError.message}`
|
|
1973
|
-
);
|
|
2742
|
+
return embeddingError(provider, `${originalError}. Could not fetch available models`, fetchError);
|
|
1974
2743
|
}
|
|
1975
2744
|
}
|
|
1976
2745
|
function createOpenAICompatibleEmbedder(provider, defaultBaseUrl) {
|
|
@@ -1993,12 +2762,14 @@ function createOpenAICompatibleEmbedder(provider, defaultBaseUrl) {
|
|
|
1993
2762
|
body: JSON.stringify({ model, input: text })
|
|
1994
2763
|
});
|
|
1995
2764
|
if (!response.ok) {
|
|
1996
|
-
|
|
1997
|
-
|
|
2765
|
+
void response.body?.cancel().catch(() => {
|
|
2766
|
+
});
|
|
2767
|
+
const message = `HTTP ${response.status}`;
|
|
1998
2768
|
throw await buildModelError4(provider, baseUrl, apiKey, message);
|
|
1999
2769
|
}
|
|
2000
2770
|
const data = await response.json();
|
|
2001
|
-
|
|
2771
|
+
const embedding = data.data?.[0]?.embedding;
|
|
2772
|
+
return requireEmbeddingVector(embedding, provider);
|
|
2002
2773
|
};
|
|
2003
2774
|
};
|
|
2004
2775
|
}
|