@agentskit/adapters 0.13.13 → 0.14.0

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/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
- stream: async function* () {
84
+ createSource: (request) => {
85
+ const controller = new AbortController();
86
+ let activeStream = null;
87
+ let parseIter = null;
88
+ const cleanup = () => {
10
89
  try {
11
- const result = await config.send(request);
12
- const stream = result instanceof Response ? result.body : result;
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
- abort: () => {
22
- config.abort?.();
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 config.send(request);
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
- const { done, value } = await reader.read();
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
- reader?.releaseLock();
225
+ try {
226
+ reader?.releaseLock();
227
+ } catch {
228
+ }
53
229
  }
54
230
  },
55
231
  abort: () => {
56
- void reader?.cancel();
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/utils.ts
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
- for (const [, tc] of pendingToolCalls) {
145
- yield { type: "tool_call", toolCall: { id: tc.id, name: tc.name, args: tc.args || "{}" } };
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
- pendingToolCalls.clear();
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
- if (typeof delta?.content === "string") {
155
- yield { type: "text", content: delta.content };
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
- for (const [, tc] of pendingToolCalls) {
186
- yield { type: "tool_call", toolCall: { id: tc.id, name: tc.name, args: tc.args || "{}" } };
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
- pendingToolCalls.clear();
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
- type: "tool_call",
218
- toolCall: {
219
- id: tc.id,
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 { type: "tool_call", toolCall: { id: tc.id, name: tc.name, args: tc.args || "{}" } };
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 parts = event.candidates?.[0]?.content?.parts;
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: JSON.stringify(fc.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: typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments ?? {})
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 instanceof DOMException && error.name === "AbortError") return false;
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 sleep(delay);
387
- response.body?.cancel().catch(() => {
388
- });
683
+ await cancelBody(response.body);
684
+ await abortableSleep(delay, signal, sleep);
389
685
  } catch (err) {
390
686
  lastError = err;
391
- if (err instanceof DOMException && err.name === "AbortError") throw 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?.({ attempt, delayMs: delay, reason: err.message });
395
- await sleep(delay);
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
- yield { type: "error", content: `${errorLabel} error: ${response.status}` };
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 new Promise((resolve2, reject) => {
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 instanceof DOMException && err.name === "AbortError") return;
477
- yield {
478
- type: "error",
479
- content: err instanceof Error ? err.message : String(err)
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.filter((message) => message.role !== "system").map((message) => ({ role: message.role, content: message.content })),
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.filter((message) => message.role !== "system").map((message) => ({
604
- role: message.role === "assistant" ? "model" : "user",
605
- parts: [{ text: message.content }]
606
- })),
607
- systemInstruction: systemMessage ? { role: "system", parts: [{ text: systemMessage.content }] } : void 0
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&key=${apiKey}`,
1054
+ `${baseUrl}/v1beta/models/${model}:streamGenerateContent?alt=sse`,
612
1055
  {
613
1056
  method: "POST",
614
- headers: { "Content-Type": "application/json" },
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
- stream: async function* () {
694
- try {
695
- if (mode === "events" && runnable.streamEvents) {
696
- const events = await runnable.streamEvents(
697
- { messages: request.messages },
698
- { version: "v2" }
699
- );
700
- for await (const event of events) {
701
- const eventName = String(event.event ?? "");
702
- if (eventName.endsWith("_stream")) {
703
- const chunk = asText(event.data);
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
- } else if (runnable.stream) {
719
- const stream = await runnable.stream({ messages: request.messages });
720
- for await (const value of stream) {
721
- const chunk = asText(value);
722
- if (chunk) yield { type: "text", content: chunk };
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
- yield { type: "done" };
729
- } catch (error) {
730
- yield {
731
- type: "error",
732
- content: error instanceof Error ? error.message : String(error)
733
- };
1194
+ },
1195
+ abort: () => {
1196
+ controller.abort();
734
1197
  }
735
- },
736
- abort: () => {
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
- async function* parseVercelStream(stream) {
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 { type: "error", content: data || "replicate stream error" };
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 { type: "error", content: `Replicate API error: ${response.status}` };
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 { type: "error", content: prediction.error };
1512
+ yield adapterErrorChunk(prediction.error);
1003
1513
  return;
1004
1514
  }
1005
1515
  const streamUrl = prediction.urls?.stream;
1006
1516
  if (!streamUrl) {
1007
- yield { type: "error", content: "Replicate prediction has no stream URL" };
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 { type: "error", content: `Replicate stream error: ${streamResponse.status}` };
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 instanceof DOMException && err.name === "AbortError") return;
1025
- yield { type: "error", content: err instanceof Error ? err.message : String(err) };
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.filter((message) => message.role !== "system").map((message) => ({
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.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: m.content }));
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 { type: "tool_call", toolCall: { id: tc.id, name: tc.name, args: tc.args || "{}" } };
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 { type: "tool_call", toolCall: { id: tc.id, name: tc.name, args: tc.args || "{}" } };
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 { type: "done" };
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 { type: "error", content: "Bedrock: empty response body" };
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
- yield { type: "error", content: err instanceof Error ? err.message : String(err) };
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 engine.chat.completions.create({ messages, stream: true });
1335
- for await (const chunk of completion) {
1336
- if (aborted) return;
1337
- const delta = chunk.choices[0]?.delta?.content;
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
- yield { type: "error", content: err instanceof Error ? err.message : String(err) };
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
- aborted = true;
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 instanceof Promise) {
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
- const id = await maybe;
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
- throw new core.ConfigError({
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
- error = err instanceof Error ? err : new Error(String(err));
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
- const source = c.adapter.createSource(request);
1591
- sources[i] = source;
1592
- const { chunks, text, error } = await drain(source, options.timeoutMs);
1593
- return { id: c.id, chunks, text, error };
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) return;
1619
- const text = await finalText(branches);
1620
- if (!text && branches.every((b) => b.error)) {
1621
- const summary = branches.map((b) => `${b.id}: ${b.error?.message ?? "unknown"}`).join("; ");
1622
- const err = new core.AdapterError({
1623
- code: core.ErrorCodes.AK_ADAPTER_STREAM_FAILED,
1624
- message: `all ensemble branches failed (${summary})`,
1625
- hint: "Inspect each branch error via options.onBranches or the err.branchErrors field."
1626
- });
1627
- err.branchErrors = branches.map((b) => ({ id: b.id, error: b.error }));
1628
- throw err;
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 (err) {
1663
- const error = err instanceof Error ? err : new Error(String(err));
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)) throw error;
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
- branchError = new Error(
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 (err) {
1690
- branchError = err instanceof Error ? err : new Error(String(err));
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
- if (options.shouldRetry && !options.shouldRetry(error, i)) throw error;
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
- const next = await iter.next();
1703
- if (next.done) return;
1704
- yield next.value;
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
- throw new core.AdapterError({
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
- history?.push(request);
1731
- const myCall = callIndex++;
2436
+ const controller = new AbortController();
2437
+ let myCall;
1732
2438
  let cancelled = false;
1733
2439
  return {
1734
2440
  stream: async function* () {
1735
- const chunks = resolve(response, myCall, request);
1736
- let endedExplicitly = false;
1737
- for (const chunk of chunks) {
1738
- if (cancelled) return;
1739
- if (delayMs > 0) await new Promise((r) => setTimeout(r, delayMs));
1740
- yield chunk;
1741
- if (chunk.type === "done" || chunk.type === "error") {
1742
- endedExplicitly = true;
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
- const innerSource = inner.createSource(request);
2502
+ let innerSource;
1773
2503
  const captured = [];
1774
- const recordedAt = (/* @__PURE__ */ new Date()).toISOString();
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
- await sink.push({ recordedAt, request, chunks: captured });
2527
+ try {
2528
+ await sink.push({ recordedAt, request, chunks: captured });
2529
+ } catch {
2530
+ }
1784
2531
  }
1785
2532
  },
1786
- abort: () => innerSource.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
- const body = await response.text().catch(() => "");
1818
- throw new Error(
1819
- `HTTP ${response.status} from ${label} ${url}: ${body.slice(0, ERROR_BODY_PREVIEW_CHARS)}`
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", url);
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 new Error(
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 new Error(
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
- const errorBody = await response.json().catch(() => ({}));
1859
- const message = errorBody.error?.message ?? `HTTP ${response.status}`;
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
- return data.data[0].embedding;
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?key=${apiKey}`;
1870
- const response = await fetch(url);
1871
- await throwIfNotOk(response, "gemini", `${baseUrl}/v1beta/models`);
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 new Error(
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 new Error(
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?key=${apiKey}`,
2667
+ `${baseUrl}/v1beta/models/${model}:embedContent`,
1897
2668
  {
1898
2669
  method: "POST",
1899
- headers: { "Content-Type": "application/json" },
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
- const errorBody = await response.json().catch(() => ({}));
1908
- const message = errorBody.error?.message ?? `HTTP ${response.status}`;
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.values;
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", url);
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 new Error(
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 new Error(
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
- const errorBody = await response.json().catch(() => ({}));
1947
- const message = errorBody.error ?? `HTTP ${response.status}`;
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
- return data.embeddings[0];
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, url);
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 new Error(
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 new Error(
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
- const errorBody = await response.json().catch(() => ({}));
1997
- const message = errorBody.error?.message ?? `HTTP ${response.status}`;
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
- return data.data[0].embedding;
2771
+ const embedding = data.data?.[0]?.embedding;
2772
+ return requireEmbeddingVector(embedding, provider);
2002
2773
  };
2003
2774
  };
2004
2775
  }