@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/dist/index.js CHANGED
@@ -1,43 +1,206 @@
1
- export { createAdapter } from './chunk-6FPLWOVE.js';
2
- import { createStreamSource, parseAnthropicStream, parseGeminiStream, parseOllamaStream, toProviderMessages, parseOpenAIStream, openai } from './chunk-77QS3QRT.js';
3
- export { chunkText, fetchWithRetry, openai, simulateStream } from './chunk-77QS3QRT.js';
1
+ export { createAdapter } from './chunk-627FHZZY.js';
2
+ import { createStreamSource, parseAnthropicStream, parseGeminiStream, parseOllamaStream, toProviderMessages, parseOpenAIStream, openai, readSSELines } from './chunk-4KZTWGRB.js';
3
+ export { chunkText, fetchWithRetry, openai, simulateStream } from './chunk-4KZTWGRB.js';
4
+ import { raceAbort, isAbortError, adapterErrorChunk, abortableSleep, parseCompleteToolArgs } from './chunk-6CIB2QCH.js';
4
5
  import { ConfigError, ErrorCodes, AdapterError } from '@agentskit/core';
5
6
 
6
7
  // src/generic.ts
7
8
  function generic(config) {
8
9
  return {
9
10
  createSource: (request) => {
11
+ const controller = new AbortController();
10
12
  let reader = null;
11
13
  const decoder = new TextDecoder();
12
14
  return {
13
15
  stream: async function* () {
16
+ if (controller.signal.aborted) return;
14
17
  try {
15
- const stream = await config.send(request);
18
+ const stream = await raceAbort(
19
+ config.send(request, controller.signal),
20
+ controller.signal
21
+ );
22
+ if (controller.signal.aborted) {
23
+ try {
24
+ await stream.cancel();
25
+ } catch {
26
+ }
27
+ return;
28
+ }
16
29
  reader = stream.getReader();
17
30
  while (true) {
18
- const { done, value } = await reader.read();
31
+ if (controller.signal.aborted) {
32
+ try {
33
+ await reader.cancel();
34
+ } catch {
35
+ }
36
+ return;
37
+ }
38
+ const { done, value } = await raceAbort(reader.read(), controller.signal);
19
39
  if (done) break;
20
40
  const text = decoder.decode(value, { stream: true });
21
41
  yield { type: "text", content: text };
22
42
  }
43
+ if (controller.signal.aborted) return;
23
44
  yield { type: "done" };
24
45
  } catch (err) {
46
+ if (isAbortError(err) || controller.signal.aborted) return;
25
47
  yield {
26
48
  type: "error",
27
- content: err instanceof Error ? err.message : String(err)
49
+ content: err instanceof Error ? err.message : String(err),
50
+ metadata: {
51
+ error: err instanceof Error ? err : new Error(String(err))
52
+ }
28
53
  };
29
54
  } finally {
30
- reader?.releaseLock();
55
+ try {
56
+ reader?.releaseLock();
57
+ } catch {
58
+ }
31
59
  }
32
60
  },
33
61
  abort: () => {
34
- void reader?.cancel();
62
+ if (!controller.signal.aborted) controller.abort();
63
+ void reader?.cancel().catch(() => {
64
+ });
35
65
  }
36
66
  };
37
67
  }
38
68
  };
39
69
  }
40
70
 
71
+ // src/tool-history.ts
72
+ function toolInput(args) {
73
+ if (args == null) return {};
74
+ if (typeof args === "string") {
75
+ try {
76
+ const parsed = JSON.parse(args);
77
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
78
+ return parsed;
79
+ }
80
+ return { value: parsed };
81
+ } catch {
82
+ return {};
83
+ }
84
+ }
85
+ if (typeof args === "object" && !Array.isArray(args)) {
86
+ return args;
87
+ }
88
+ return { value: args };
89
+ }
90
+ function toolResponsePayload(content) {
91
+ try {
92
+ const parsed = JSON.parse(content);
93
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
94
+ return parsed;
95
+ }
96
+ return { result: parsed };
97
+ } catch {
98
+ return { result: content };
99
+ }
100
+ }
101
+ function toAnthropicMessages(messages) {
102
+ const knownNames = /* @__PURE__ */ new Map();
103
+ const output = [];
104
+ for (const message of messages) {
105
+ if (message.role === "system") continue;
106
+ if (message.role === "assistant" && message.toolCalls && message.toolCalls.length > 0) {
107
+ for (const tc of message.toolCalls) knownNames.set(tc.id, tc.name);
108
+ const blocks = [];
109
+ if (message.content) {
110
+ blocks.push({ type: "text", text: message.content });
111
+ }
112
+ for (const tc of message.toolCalls) {
113
+ blocks.push({
114
+ type: "tool_use",
115
+ id: tc.id,
116
+ name: tc.name,
117
+ input: toolInput(tc.args)
118
+ });
119
+ }
120
+ output.push({ role: "assistant", content: blocks });
121
+ continue;
122
+ }
123
+ if (message.role === "tool") {
124
+ const id = message.toolCallId;
125
+ if (!id || !knownNames.has(id)) continue;
126
+ const block = {
127
+ type: "tool_result",
128
+ tool_use_id: id,
129
+ content: message.content
130
+ };
131
+ const previous2 = output[output.length - 1];
132
+ if (previous2?.role === "user" && Array.isArray(previous2.content) && previous2.content.every((item) => item && typeof item === "object" && item.type === "tool_result")) {
133
+ previous2.content.push(block);
134
+ } else {
135
+ output.push({ role: "user", content: [block] });
136
+ }
137
+ continue;
138
+ }
139
+ if (message.role === "assistant" && !message.content) continue;
140
+ const previous = output[output.length - 1];
141
+ if (message.role === "user" && previous?.role === "user") {
142
+ const previousBlocks = Array.isArray(previous.content) ? previous.content : [{ type: "text", text: previous.content }];
143
+ previous.content = [...previousBlocks, { type: "text", text: message.content }];
144
+ } else {
145
+ output.push({ role: message.role, content: message.content });
146
+ }
147
+ }
148
+ return output;
149
+ }
150
+ function toGeminiContents(messages) {
151
+ const knownNames = /* @__PURE__ */ new Map();
152
+ const output = [];
153
+ for (const message of messages) {
154
+ if (message.role === "system") continue;
155
+ if (message.role === "assistant" && message.toolCalls && message.toolCalls.length > 0) {
156
+ for (const tc of message.toolCalls) knownNames.set(tc.id, tc.name);
157
+ const parts = [];
158
+ if (message.content) {
159
+ parts.push({ text: message.content });
160
+ }
161
+ for (const tc of message.toolCalls) {
162
+ parts.push({
163
+ functionCall: {
164
+ id: tc.id,
165
+ name: tc.name,
166
+ args: toolInput(tc.args)
167
+ }
168
+ });
169
+ }
170
+ output.push({ role: "model", parts });
171
+ continue;
172
+ }
173
+ if (message.role === "tool") {
174
+ const id = message.toolCallId;
175
+ const name = id ? knownNames.get(id) : void 0;
176
+ if (!name) continue;
177
+ const part = {
178
+ functionResponse: {
179
+ id,
180
+ name,
181
+ response: toolResponsePayload(message.content)
182
+ }
183
+ };
184
+ const previous2 = output[output.length - 1];
185
+ if (previous2?.role === "user" && previous2.parts.every((item) => item.functionResponse !== void 0)) {
186
+ previous2.parts.push(part);
187
+ } else {
188
+ output.push({ role: "user", parts: [part] });
189
+ }
190
+ continue;
191
+ }
192
+ if (message.role === "assistant" && !message.content) continue;
193
+ const role = message.role === "assistant" ? "model" : "user";
194
+ const previous = output[output.length - 1];
195
+ if (previous?.role === role) {
196
+ previous.parts.push({ text: message.content });
197
+ } else {
198
+ output.push({ role, parts: [{ text: message.content }] });
199
+ }
200
+ }
201
+ return output;
202
+ }
203
+
41
204
  // src/anthropic.ts
42
205
  function anthropic(config) {
43
206
  const { apiKey, model, baseUrl = "https://api.anthropic.com", maxTokens = 4096, retry } = config;
@@ -53,7 +216,7 @@ function anthropic(config) {
53
216
  const body = {
54
217
  model,
55
218
  max_tokens: request.context?.maxTokens ?? maxTokens,
56
- messages: request.messages.filter((message) => message.role !== "system").map((message) => ({ role: message.role, content: message.content })),
219
+ messages: toAnthropicMessages(request.messages),
57
220
  system: request.messages.find((message) => message.role === "system")?.content,
58
221
  tools: request.context?.tools?.map((tool) => ({
59
222
  name: tool.name,
@@ -94,18 +257,25 @@ function gemini(config) {
94
257
  createSource: (request) => {
95
258
  const systemMessage = request.messages.find((message) => message.role === "system");
96
259
  const body = {
97
- contents: request.messages.filter((message) => message.role !== "system").map((message) => ({
98
- role: message.role === "assistant" ? "model" : "user",
99
- parts: [{ text: message.content }]
100
- })),
101
- systemInstruction: systemMessage ? { role: "system", parts: [{ text: systemMessage.content }] } : void 0
260
+ contents: toGeminiContents(request.messages),
261
+ systemInstruction: systemMessage ? { role: "system", parts: [{ text: systemMessage.content }] } : void 0,
262
+ tools: request.context?.tools && request.context.tools.length > 0 ? [{
263
+ functionDeclarations: request.context.tools.map((tool) => ({
264
+ name: tool.name,
265
+ description: tool.description,
266
+ parameters: tool.schema
267
+ }))
268
+ }] : void 0
102
269
  };
103
270
  return createStreamSource(
104
271
  (signal) => fetch(
105
- `${baseUrl}/v1beta/models/${model}:streamGenerateContent?alt=sse&key=${apiKey}`,
272
+ `${baseUrl}/v1beta/models/${model}:streamGenerateContent?alt=sse`,
106
273
  {
107
274
  method: "POST",
108
- headers: { "Content-Type": "application/json" },
275
+ headers: {
276
+ "Content-Type": "application/json",
277
+ "x-goog-api-key": apiKey
278
+ },
109
279
  body: JSON.stringify(body),
110
280
  signal
111
281
  }
@@ -180,56 +350,71 @@ function asText(value) {
180
350
  }
181
351
  return "";
182
352
  }
353
+ function isToolStartEvent(eventName) {
354
+ return eventName === "on_tool_start" || eventName.endsWith("_tool_start");
355
+ }
183
356
  function langchain(config) {
184
357
  const { runnable, mode = "stream" } = config;
185
358
  return {
186
- createSource: (request) => ({
187
- stream: async function* () {
188
- try {
189
- if (mode === "events" && runnable.streamEvents) {
190
- const events = await runnable.streamEvents(
191
- { messages: request.messages },
192
- { version: "v2" }
193
- );
194
- for await (const event of events) {
195
- const eventName = String(event.event ?? "");
196
- if (eventName.endsWith("_stream")) {
197
- const chunk = asText(event.data);
359
+ createSource: (request) => {
360
+ const controller = new AbortController();
361
+ return {
362
+ stream: async function* () {
363
+ if (controller.signal.aborted) return;
364
+ try {
365
+ const runnableConfig = {
366
+ version: "v2",
367
+ signal: controller.signal
368
+ };
369
+ if (mode === "events" && runnable.streamEvents) {
370
+ const events = await runnable.streamEvents(
371
+ { messages: request.messages },
372
+ runnableConfig
373
+ );
374
+ for await (const event of events) {
375
+ if (controller.signal.aborted) return;
376
+ const eventName = String(event.event ?? "");
377
+ if (eventName.endsWith("_stream")) {
378
+ const chunk = asText(event.data);
379
+ if (chunk) yield { type: "text", content: chunk };
380
+ } else if (isToolStartEvent(eventName) && event.name) {
381
+ yield {
382
+ type: "tool_call",
383
+ toolCall: {
384
+ id: String(event.run_id ?? `${event.name}-${Date.now()}`),
385
+ name: String(event.name),
386
+ args: JSON.stringify(event.data ?? {})
387
+ }
388
+ };
389
+ }
390
+ }
391
+ } else if (runnable.stream) {
392
+ const stream = await runnable.stream(
393
+ { messages: request.messages },
394
+ runnableConfig
395
+ );
396
+ for await (const value of stream) {
397
+ if (controller.signal.aborted) return;
398
+ const chunk = asText(value);
198
399
  if (chunk) yield { type: "text", content: chunk };
199
- } else if (eventName.endsWith("_start") && event.name) {
200
- yield {
201
- type: "tool_call",
202
- toolCall: {
203
- id: String(event.run_id ?? `${event.name}-${Date.now()}`),
204
- name: String(event.name),
205
- args: JSON.stringify(event.data ?? {})
206
- }
207
- };
208
- } else if (eventName.endsWith("_end")) {
209
- continue;
210
400
  }
401
+ } else {
402
+ yield adapterErrorChunk("Runnable does not implement stream() or streamEvents()");
403
+ return;
211
404
  }
212
- } else if (runnable.stream) {
213
- const stream = await runnable.stream({ messages: request.messages });
214
- for await (const value of stream) {
215
- const chunk = asText(value);
216
- if (chunk) yield { type: "text", content: chunk };
217
- }
218
- } else {
219
- yield { type: "error", content: "Runnable does not implement stream() or streamEvents()" };
220
- return;
405
+ if (controller.signal.aborted) return;
406
+ yield { type: "done" };
407
+ } catch (error) {
408
+ if (isAbortError(error) || controller.signal.aborted) return;
409
+ const message = error instanceof Error ? error.message : String(error);
410
+ yield adapterErrorChunk(message, { cause: error });
221
411
  }
222
- yield { type: "done" };
223
- } catch (error) {
224
- yield {
225
- type: "error",
226
- content: error instanceof Error ? error.message : String(error)
227
- };
412
+ },
413
+ abort: () => {
414
+ controller.abort();
228
415
  }
229
- },
230
- abort: () => {
231
- }
232
- })
416
+ };
417
+ }
233
418
  };
234
419
  }
235
420
  function langgraph(config) {
@@ -240,7 +425,8 @@ function langgraph(config) {
240
425
  }
241
426
 
242
427
  // src/vercel-ai.ts
243
- async function* parseVercelStream(stream) {
428
+ var UI_STREAM_HEADER = "x-vercel-ai-ui-message-stream";
429
+ async function* parseVercelTextStream(stream) {
244
430
  const reader = stream.getReader();
245
431
  const decoder = new TextDecoder();
246
432
  try {
@@ -255,6 +441,48 @@ async function* parseVercelStream(stream) {
255
441
  }
256
442
  yield { type: "done" };
257
443
  }
444
+ async function* parseVercelUiMessageStreamV1(stream) {
445
+ for await (const data of readSSELines(stream)) {
446
+ if (data === "[DONE]") {
447
+ yield { type: "done" };
448
+ return;
449
+ }
450
+ try {
451
+ const part = JSON.parse(data);
452
+ const type = part.type;
453
+ if (type === "text-delta") {
454
+ const delta = part.delta ?? part.textDelta;
455
+ if (typeof delta === "string" && delta) {
456
+ yield { type: "text", content: delta };
457
+ }
458
+ } else if (type === "reasoning-delta") {
459
+ const delta = part.delta ?? part.textDelta;
460
+ if (typeof delta === "string" && delta) {
461
+ yield { type: "reasoning", content: delta };
462
+ }
463
+ } else if (type === "error") {
464
+ const message = part.errorText ?? part.message ?? "Vercel AI UI stream error";
465
+ yield adapterErrorChunk(String(message));
466
+ return;
467
+ } else if (type === "abort") {
468
+ yield adapterErrorChunk(part.reason ?? "Vercel AI UI stream aborted by provider");
469
+ return;
470
+ } else if (type === "finish") {
471
+ continue;
472
+ }
473
+ } catch {
474
+ }
475
+ }
476
+ yield adapterErrorChunk("Vercel AI UI stream truncated before [DONE]");
477
+ }
478
+ async function* parseVercelStream(stream, response) {
479
+ const header = response?.headers.get(UI_STREAM_HEADER) ?? response?.headers.get(UI_STREAM_HEADER.toLowerCase());
480
+ if (header === "v1") {
481
+ yield* parseVercelUiMessageStreamV1(stream);
482
+ return;
483
+ }
484
+ yield* parseVercelTextStream(stream);
485
+ }
258
486
  function vercelAI(config) {
259
487
  const { api, headers = {}, retry } = config;
260
488
  return {
@@ -428,6 +656,7 @@ async function* parseReplicateStream(stream) {
428
656
  const decoder = new TextDecoder();
429
657
  let buffer = "";
430
658
  let currentEvent = "";
659
+ let sawDone = false;
431
660
  try {
432
661
  while (true) {
433
662
  const { done, value } = await reader.read();
@@ -445,10 +674,11 @@ async function* parseReplicateStream(stream) {
445
674
  if (currentEvent === "output" && data) {
446
675
  yield { type: "text", content: data };
447
676
  } else if (currentEvent === "done") {
677
+ sawDone = true;
448
678
  yield { type: "done" };
449
679
  return;
450
680
  } else if (currentEvent === "error") {
451
- yield { type: "error", content: data || "replicate stream error" };
681
+ yield adapterErrorChunk(data || "replicate stream error");
452
682
  return;
453
683
  }
454
684
  }
@@ -456,6 +686,10 @@ async function* parseReplicateStream(stream) {
456
686
  } finally {
457
687
  reader.releaseLock();
458
688
  }
689
+ if (!sawDone) {
690
+ yield adapterErrorChunk("Replicate stream ended without done event");
691
+ return;
692
+ }
459
693
  yield { type: "done" };
460
694
  }
461
695
  function replicate(config) {
@@ -488,17 +722,17 @@ function replicate(config) {
488
722
  signal: controller.signal
489
723
  });
490
724
  if (!response.ok) {
491
- yield { type: "error", content: `Replicate API error: ${response.status}` };
725
+ yield adapterErrorChunk(`Replicate API error: ${response.status}`);
492
726
  return;
493
727
  }
494
728
  const prediction = await response.json();
495
729
  if (prediction.error) {
496
- yield { type: "error", content: prediction.error };
730
+ yield adapterErrorChunk(prediction.error);
497
731
  return;
498
732
  }
499
733
  const streamUrl = prediction.urls?.stream;
500
734
  if (!streamUrl) {
501
- yield { type: "error", content: "Replicate prediction has no stream URL" };
735
+ yield adapterErrorChunk("Replicate prediction has no stream URL");
502
736
  return;
503
737
  }
504
738
  const streamResponse = await fetch(streamUrl, {
@@ -507,7 +741,7 @@ function replicate(config) {
507
741
  signal: controller.signal
508
742
  });
509
743
  if (!streamResponse.ok || !streamResponse.body) {
510
- yield { type: "error", content: `Replicate stream error: ${streamResponse.status}` };
744
+ yield adapterErrorChunk(`Replicate stream error: ${streamResponse.status}`);
511
745
  return;
512
746
  }
513
747
  for await (const chunk of parseReplicateStream(streamResponse.body)) {
@@ -515,8 +749,9 @@ function replicate(config) {
515
749
  yield chunk;
516
750
  }
517
751
  } catch (err) {
518
- if (err instanceof DOMException && err.name === "AbortError") return;
519
- yield { type: "error", content: err instanceof Error ? err.message : String(err) };
752
+ if (isAbortError(err) || aborted) return;
753
+ const message = err instanceof Error ? err.message : String(err);
754
+ yield adapterErrorChunk(message, { cause: err });
520
755
  }
521
756
  },
522
757
  abort: () => {
@@ -566,10 +801,7 @@ function vertex(config) {
566
801
  createSource: (request) => {
567
802
  const systemMessage = request.messages.find((message) => message.role === "system");
568
803
  const body = {
569
- contents: request.messages.filter((message) => message.role !== "system").map((message) => ({
570
- role: message.role === "assistant" ? "model" : "user",
571
- parts: [{ text: message.content }]
572
- })),
804
+ contents: toGeminiContents(request.messages),
573
805
  systemInstruction: systemMessage ? { role: "system", parts: [{ text: systemMessage.content }] } : void 0,
574
806
  tools: request.context?.tools && request.context.tools.length > 0 ? [{
575
807
  functionDeclarations: request.context.tools.map((tool) => ({
@@ -625,7 +857,7 @@ function isAnthropicModel(model) {
625
857
  return model.startsWith("anthropic.");
626
858
  }
627
859
  function buildAnthropicBody(request, maxTokens) {
628
- const messages = request.messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: m.content }));
860
+ const messages = toAnthropicMessages(request.messages);
629
861
  const system = request.messages.find((m) => m.role === "system")?.content;
630
862
  const tools = request.context?.tools?.map((tool) => ({
631
863
  name: tool.name,
@@ -644,6 +876,19 @@ function buildAnthropicBody(request, maxTokens) {
644
876
  async function* parseAnthropicBedrockEvents(events) {
645
877
  const decoder = new TextDecoder();
646
878
  const pendingToolCalls = /* @__PURE__ */ new Map();
879
+ const emitTool = function* (tc) {
880
+ const parsed = parseCompleteToolArgs(tc.args);
881
+ if (!parsed.ok) {
882
+ yield {
883
+ type: "error",
884
+ content: parsed.error.message,
885
+ metadata: { error: parsed.error }
886
+ };
887
+ return false;
888
+ }
889
+ yield { type: "tool_call", toolCall: { id: tc.id, name: tc.name, args: parsed.args } };
890
+ return true;
891
+ };
647
892
  for await (const item of events) {
648
893
  const bytes = item.chunk?.bytes;
649
894
  if (!bytes) continue;
@@ -673,7 +918,10 @@ async function* parseAnthropicBedrockEvents(events) {
673
918
  const index = event.index ?? 0;
674
919
  const tc = pendingToolCalls.get(index);
675
920
  if (tc) {
676
- yield { type: "tool_call", toolCall: { id: tc.id, name: tc.name, args: tc.args || "{}" } };
921
+ if (!(yield* emitTool(tc))) {
922
+ pendingToolCalls.clear();
923
+ return;
924
+ }
677
925
  pendingToolCalls.delete(index);
678
926
  }
679
927
  } else if (type === "message_delta") {
@@ -700,18 +948,18 @@ async function* parseAnthropicBedrockEvents(events) {
700
948
  }
701
949
  } else if (type === "message_stop") {
702
950
  for (const [, tc] of pendingToolCalls) {
703
- yield { type: "tool_call", toolCall: { id: tc.id, name: tc.name, args: tc.args || "{}" } };
951
+ if (!(yield* emitTool(tc))) {
952
+ pendingToolCalls.clear();
953
+ return;
954
+ }
704
955
  }
705
956
  pendingToolCalls.clear();
706
957
  yield { type: "done" };
707
958
  return;
708
959
  }
709
960
  }
710
- for (const [, tc] of pendingToolCalls) {
711
- yield { type: "tool_call", toolCall: { id: tc.id, name: tc.name, args: tc.args || "{}" } };
712
- }
713
961
  pendingToolCalls.clear();
714
- yield { type: "done" };
962
+ yield adapterErrorChunk("Bedrock stream ended before message_stop");
715
963
  }
716
964
  function bedrock(config) {
717
965
  const { model, region, maxTokens = 4096 } = config;
@@ -746,9 +994,10 @@ function bedrock(config) {
746
994
  let aborted = false;
747
995
  return {
748
996
  stream: async function* () {
749
- if (aborted) return;
997
+ if (aborted || controller.signal.aborted) return;
750
998
  try {
751
999
  const client = await getClient();
1000
+ if (aborted || controller.signal.aborted) return;
752
1001
  const sdk = config.client ? null : await loadSdk();
753
1002
  const input = {
754
1003
  modelId: model,
@@ -757,17 +1006,19 @@ function bedrock(config) {
757
1006
  body: buildAnthropicBody(request, maxTokens)
758
1007
  };
759
1008
  const command = sdk ? new sdk.InvokeModelWithResponseStreamCommand(input) : { input };
760
- const response = await client.send(command);
1009
+ const response = await client.send(command, { abortSignal: controller.signal });
761
1010
  if (!response.body) {
762
- yield { type: "error", content: "Bedrock: empty response body" };
1011
+ yield adapterErrorChunk("Bedrock: empty response body");
763
1012
  return;
764
1013
  }
765
1014
  for await (const chunk of parseAnthropicBedrockEvents(response.body)) {
766
- if (aborted) return;
1015
+ if (aborted || controller.signal.aborted) return;
767
1016
  yield chunk;
768
1017
  }
769
1018
  } catch (err) {
770
- yield { type: "error", content: err instanceof Error ? err.message : String(err) };
1019
+ if (isAbortError(err) || aborted) return;
1020
+ const message = err instanceof Error ? err.message : String(err);
1021
+ yield adapterErrorChunk(message, { cause: err });
771
1022
  }
772
1023
  },
773
1024
  abort: () => {
@@ -818,26 +1069,62 @@ function webllm(config) {
818
1069
  tools: false
819
1070
  },
820
1071
  createSource: (request) => {
1072
+ const controller = new AbortController();
821
1073
  let aborted = false;
1074
+ let activeIter = null;
1075
+ const softAbort = () => {
1076
+ aborted = true;
1077
+ if (!controller.signal.aborted) controller.abort();
1078
+ if (activeIter && typeof activeIter.return === "function") {
1079
+ void activeIter.return(void 0).catch(() => {
1080
+ });
1081
+ }
1082
+ };
822
1083
  return {
823
1084
  stream: async function* () {
824
- if (aborted) return;
1085
+ if (aborted || controller.signal.aborted) return;
825
1086
  try {
826
- const engine = await getEngine();
1087
+ const engine = await raceAbort(getEngine(), controller.signal);
1088
+ if (aborted || controller.signal.aborted) return;
827
1089
  const messages = request.messages.map((m) => ({ role: m.role, content: m.content }));
828
- const completion = await engine.chat.completions.create({ messages, stream: true });
829
- for await (const chunk of completion) {
830
- if (aborted) return;
831
- const delta = chunk.choices[0]?.delta?.content;
1090
+ const completion = await raceAbort(
1091
+ Promise.resolve(
1092
+ engine.chat.completions.create({ messages, stream: true })
1093
+ ),
1094
+ controller.signal
1095
+ );
1096
+ if (aborted || controller.signal.aborted) return;
1097
+ activeIter = completion[Symbol.asyncIterator]();
1098
+ while (true) {
1099
+ if (aborted || controller.signal.aborted) {
1100
+ if (typeof activeIter.return === "function") {
1101
+ await activeIter.return(void 0).catch(() => {
1102
+ });
1103
+ }
1104
+ return;
1105
+ }
1106
+ const next = await raceAbort(activeIter.next(), controller.signal);
1107
+ if (next.done) break;
1108
+ if (aborted || controller.signal.aborted) {
1109
+ if (typeof activeIter.return === "function") {
1110
+ await activeIter.return(void 0).catch(() => {
1111
+ });
1112
+ }
1113
+ return;
1114
+ }
1115
+ const delta = next.value.choices[0]?.delta?.content;
832
1116
  if (delta) yield { type: "text", content: delta };
833
1117
  }
1118
+ if (aborted || controller.signal.aborted) return;
834
1119
  yield { type: "done" };
835
1120
  } catch (err) {
836
- yield { type: "error", content: err instanceof Error ? err.message : String(err) };
1121
+ if (isAbortError(err) || aborted || controller.signal.aborted) return;
1122
+ const message = err instanceof Error ? err.message : String(err);
1123
+ yield adapterErrorChunk(message, { cause: err });
837
1124
  }
838
1125
  },
839
1126
  abort: () => {
840
- aborted = true;
1127
+ softAbort();
841
1128
  }
842
1129
  };
843
1130
  }
@@ -937,22 +1224,57 @@ function createRouter(options) {
937
1224
  }
938
1225
  if (typeof policy === "function") {
939
1226
  const maybe = policy({ request, candidates: pool });
940
- if (maybe instanceof Promise) {
1227
+ if (typeof maybe !== "string") {
1228
+ const controller = new AbortController();
1229
+ let child;
941
1230
  return {
942
1231
  abort: () => {
1232
+ controller.abort();
1233
+ child?.abort();
943
1234
  },
944
1235
  stream: async function* () {
945
- const id = await maybe;
1236
+ let id;
1237
+ try {
1238
+ id = await raceAbort(maybe, controller.signal);
1239
+ } catch (err) {
1240
+ if (isAbortError(err)) return;
1241
+ const error = err instanceof Error ? err : new Error(String(err));
1242
+ yield {
1243
+ type: "error",
1244
+ content: error.message,
1245
+ metadata: { error }
1246
+ };
1247
+ return;
1248
+ }
946
1249
  const c3 = pool.find((x) => x.id === id);
947
1250
  if (!c3) {
948
- throw new ConfigError({
1251
+ const error = new ConfigError({
949
1252
  code: ErrorCodes.AK_CONFIG_INVALID,
950
1253
  message: `policy returned unknown id: ${id}`,
951
1254
  hint: "Custom policy functions must return one of the candidate ids."
952
1255
  });
1256
+ yield {
1257
+ type: "error",
1258
+ content: error.message,
1259
+ metadata: { error }
1260
+ };
1261
+ return;
1262
+ }
1263
+ try {
1264
+ options.onRoute?.({ id: c3.id, reason: pickedBy ?? "custom policy", request });
1265
+ child = c3.adapter.createSource(request);
1266
+ for await (const chunk of child.stream()) {
1267
+ yield chunk;
1268
+ }
1269
+ } catch (err) {
1270
+ if (controller.signal.aborted || isAbortError(err)) return;
1271
+ const error = err instanceof Error ? err : new Error(String(err));
1272
+ yield {
1273
+ type: "error",
1274
+ content: error.message,
1275
+ metadata: { error }
1276
+ };
953
1277
  }
954
- options.onRoute?.({ id: c3.id, reason: pickedBy ?? "custom policy", request });
955
- for await (const chunk of c3.adapter.createSource(request).stream()) yield chunk;
956
1278
  }
957
1279
  };
958
1280
  }
@@ -1040,9 +1362,17 @@ async function drain(source, timeoutMs) {
1040
1362
  for await (const chunk of source.stream()) {
1041
1363
  chunks.push(chunk);
1042
1364
  if (chunk.type === "text" && typeof chunk.content === "string") text += chunk.content;
1365
+ if (chunk.type === "error") {
1366
+ const metaErr = chunk.metadata?.error;
1367
+ error = metaErr instanceof Error ? metaErr : new Error(chunk.content ?? "stream error");
1368
+ }
1043
1369
  }
1044
1370
  } catch (err) {
1045
- error = err instanceof Error ? err : new Error(String(err));
1371
+ if (!isAbortError(err)) {
1372
+ error = err instanceof Error ? err : new Error(String(err));
1373
+ } else if (!timedOut) {
1374
+ error = err instanceof Error ? err : new Error(String(err));
1375
+ }
1046
1376
  } finally {
1047
1377
  if (timer) clearTimeout(timer);
1048
1378
  }
@@ -1081,10 +1411,19 @@ function createEnsembleAdapter(options) {
1081
1411
  const sources = [];
1082
1412
  const run = async () => {
1083
1413
  const tasks = options.candidates.map(async (c, i) => {
1084
- const source = c.adapter.createSource(request);
1085
- sources[i] = source;
1086
- const { chunks, text, error } = await drain(source, options.timeoutMs);
1087
- return { id: c.id, chunks, text, error };
1414
+ try {
1415
+ const source = c.adapter.createSource(request);
1416
+ sources[i] = source;
1417
+ if (aborted) {
1418
+ source.abort();
1419
+ return { id: c.id, chunks: [], text: "", error: new Error("aborted") };
1420
+ }
1421
+ const { chunks, text, error } = await drain(source, options.timeoutMs);
1422
+ return { id: c.id, chunks, text, error };
1423
+ } catch (err) {
1424
+ const error = err instanceof Error ? err : new Error(String(err));
1425
+ return { id: c.id, chunks: [], text: "", error };
1426
+ }
1088
1427
  });
1089
1428
  return Promise.all(tasks);
1090
1429
  };
@@ -1107,22 +1446,51 @@ function createEnsembleAdapter(options) {
1107
1446
  for (const s of sources) s?.abort();
1108
1447
  },
1109
1448
  stream: async function* () {
1449
+ if (aborted) return;
1110
1450
  const branches = await run();
1111
1451
  options.onBranches?.(branches);
1112
- if (aborted) return;
1113
- const text = await finalText(branches);
1114
- if (!text && branches.every((b) => b.error)) {
1115
- const summary = branches.map((b) => `${b.id}: ${b.error?.message ?? "unknown"}`).join("; ");
1116
- const err = new AdapterError({
1117
- code: ErrorCodes.AK_ADAPTER_STREAM_FAILED,
1118
- message: `all ensemble branches failed (${summary})`,
1119
- hint: "Inspect each branch error via options.onBranches or the err.branchErrors field."
1120
- });
1121
- err.branchErrors = branches.map((b) => ({ id: b.id, error: b.error }));
1122
- throw err;
1452
+ if (aborted) {
1453
+ for (const s of sources) s?.abort();
1454
+ return;
1455
+ }
1456
+ try {
1457
+ const text = await finalText(branches);
1458
+ if (!text && branches.every((b) => b.error)) {
1459
+ const summary = branches.map((b) => `${b.id}: ${b.error?.message ?? "unknown"}`).join("; ");
1460
+ const err = new AdapterError({
1461
+ code: ErrorCodes.AK_ADAPTER_STREAM_FAILED,
1462
+ message: `all ensemble branches failed (${summary})`,
1463
+ hint: "Inspect each branch error via options.onBranches or the err.branchErrors field."
1464
+ });
1465
+ err.branchErrors = branches.map((b) => ({ id: b.id, error: b.error }));
1466
+ yield {
1467
+ type: "error",
1468
+ content: err.message,
1469
+ metadata: { error: err }
1470
+ };
1471
+ return;
1472
+ }
1473
+ yield {
1474
+ type: "text",
1475
+ content: text,
1476
+ metadata: {
1477
+ ensemble: branches.map((b) => ({
1478
+ id: b.id,
1479
+ bytes: b.text.length,
1480
+ error: b.error?.message
1481
+ }))
1482
+ }
1483
+ };
1484
+ yield { type: "done" };
1485
+ } catch (err) {
1486
+ if (aborted || isAbortError(err)) return;
1487
+ const error = err instanceof Error ? err : new Error(String(err));
1488
+ yield {
1489
+ type: "error",
1490
+ content: error.message,
1491
+ metadata: { error }
1492
+ };
1123
1493
  }
1124
- yield { type: "text", content: text, metadata: { ensemble: branches.map((b) => ({ id: b.id, bytes: b.text.length, error: b.error?.message })) } };
1125
- yield { type: "done" };
1126
1494
  }
1127
1495
  };
1128
1496
  }
@@ -1153,57 +1521,119 @@ function createFallbackAdapter(candidates, options = {}) {
1153
1521
  let source;
1154
1522
  try {
1155
1523
  source = candidate.adapter.createSource(request);
1156
- } catch (err) {
1157
- const error = err instanceof Error ? err : new Error(String(err));
1524
+ } catch (err2) {
1525
+ const error = err2 instanceof Error ? err2 : new Error(String(err2));
1158
1526
  errors.push({ id: candidate.id, error });
1159
1527
  options.onFallback?.({ id: candidate.id, index: i, error });
1160
- if (options.shouldRetry && !options.shouldRetry(error, i)) throw error;
1528
+ if (options.shouldRetry && !options.shouldRetry(error, i)) {
1529
+ yield {
1530
+ type: "error",
1531
+ content: error.message,
1532
+ metadata: { error }
1533
+ };
1534
+ return;
1535
+ }
1161
1536
  continue;
1162
1537
  }
1163
1538
  active = source;
1164
1539
  const iter = source.stream()[Symbol.asyncIterator]();
1165
1540
  let firstReal;
1166
1541
  let branchError;
1542
+ const leadingUsage = [];
1167
1543
  try {
1168
1544
  while (true) {
1545
+ if (aborted) {
1546
+ source.abort();
1547
+ return;
1548
+ }
1169
1549
  const r = await iter.next();
1170
1550
  if (r.done) {
1171
1551
  branchError = new Error(`candidate ${candidate.id} emitted no chunks`);
1172
1552
  break;
1173
1553
  }
1174
1554
  if (r.value.type === "error") {
1175
- branchError = new Error(
1555
+ const metaErr = r.value.metadata?.error;
1556
+ branchError = metaErr instanceof Error ? metaErr : new Error(
1176
1557
  `candidate ${candidate.id}: ${r.value.content ?? "stream error"}`
1177
1558
  );
1178
1559
  break;
1179
1560
  }
1561
+ if (r.value.type === "usage") {
1562
+ leadingUsage.push(r.value);
1563
+ continue;
1564
+ }
1180
1565
  firstReal = r.value;
1181
1566
  break;
1182
1567
  }
1183
- } catch (err) {
1184
- branchError = err instanceof Error ? err : new Error(String(err));
1568
+ } catch (err2) {
1569
+ if (isAbortError(err2) || aborted) {
1570
+ source.abort();
1571
+ return;
1572
+ }
1573
+ branchError = err2 instanceof Error ? err2 : new Error(String(err2));
1185
1574
  }
1186
1575
  if (branchError || !firstReal) {
1187
1576
  const error = branchError ?? new Error(`candidate ${candidate.id} emitted no chunks`);
1188
1577
  errors.push({ id: candidate.id, error });
1189
1578
  options.onFallback?.({ id: candidate.id, index: i, error });
1190
- if (options.shouldRetry && !options.shouldRetry(error, i)) throw error;
1579
+ try {
1580
+ source.abort();
1581
+ } catch {
1582
+ }
1583
+ if (typeof iter.return === "function") {
1584
+ try {
1585
+ await iter.return(void 0);
1586
+ } catch {
1587
+ }
1588
+ }
1589
+ if (options.shouldRetry && !options.shouldRetry(error, i)) {
1590
+ yield {
1591
+ type: "error",
1592
+ content: error.message,
1593
+ metadata: { error }
1594
+ };
1595
+ return;
1596
+ }
1191
1597
  active = void 0;
1192
1598
  continue;
1193
1599
  }
1600
+ for (const chunk of leadingUsage) yield chunk;
1194
1601
  yield firstReal;
1195
1602
  while (true) {
1196
- const next = await iter.next();
1197
- if (next.done) return;
1198
- yield next.value;
1603
+ if (aborted) {
1604
+ source.abort();
1605
+ return;
1606
+ }
1607
+ try {
1608
+ const next = await iter.next();
1609
+ if (next.done) return;
1610
+ yield next.value;
1611
+ } catch (err2) {
1612
+ if (isAbortError(err2) || aborted) {
1613
+ source.abort();
1614
+ return;
1615
+ }
1616
+ const error = err2 instanceof Error ? err2 : new Error(String(err2));
1617
+ yield {
1618
+ type: "error",
1619
+ content: error.message,
1620
+ metadata: { error }
1621
+ };
1622
+ return;
1623
+ }
1199
1624
  }
1200
1625
  }
1201
1626
  const summary = errors.map((e) => `${e.id}: ${e.error.message}`).join("; ");
1202
- throw new AdapterError({
1627
+ const err = new AdapterError({
1203
1628
  code: ErrorCodes.AK_ADAPTER_STREAM_FAILED,
1204
1629
  message: `all fallback candidates failed (${summary})`,
1205
1630
  hint: "Check each candidate adapter's configuration and provider keys."
1206
1631
  });
1632
+ yield {
1633
+ type: "error",
1634
+ content: err.message,
1635
+ metadata: { error: err }
1636
+ };
1207
1637
  }
1208
1638
  };
1209
1639
  }
@@ -1221,25 +1651,48 @@ function mockAdapter(options) {
1221
1651
  usage: true
1222
1652
  },
1223
1653
  createSource: (request) => {
1224
- history?.push(request);
1225
- const myCall = callIndex++;
1654
+ const controller = new AbortController();
1655
+ let myCall;
1226
1656
  let cancelled = false;
1227
1657
  return {
1228
1658
  stream: async function* () {
1229
- const chunks = resolve(response, myCall, request);
1230
- let endedExplicitly = false;
1231
- for (const chunk of chunks) {
1232
- if (cancelled) return;
1233
- if (delayMs > 0) await new Promise((r) => setTimeout(r, delayMs));
1234
- yield chunk;
1235
- if (chunk.type === "done" || chunk.type === "error") {
1236
- endedExplicitly = true;
1659
+ if (cancelled || controller.signal.aborted) return;
1660
+ try {
1661
+ if (myCall === void 0) {
1662
+ myCall = callIndex++;
1663
+ history?.push(request);
1664
+ }
1665
+ const chunks = resolve(response, myCall, request);
1666
+ for (const chunk of chunks) {
1667
+ if (cancelled || controller.signal.aborted) return;
1668
+ if (delayMs > 0) {
1669
+ await abortableSleep(delayMs, controller.signal, (ms) => new Promise((resolveTimer) => setTimeout(resolveTimer, ms)));
1670
+ }
1671
+ if (cancelled || controller.signal.aborted) return;
1672
+ if (chunk.type === "error" && !(chunk.metadata?.error instanceof Error)) {
1673
+ const normalized = adapterErrorChunk(
1674
+ chunk.content ?? "Mock adapter error",
1675
+ { cause: chunk.metadata?.error }
1676
+ );
1677
+ yield {
1678
+ ...chunk,
1679
+ metadata: { ...chunk.metadata, ...normalized.metadata }
1680
+ };
1681
+ return;
1682
+ }
1683
+ yield chunk;
1684
+ if (chunk.type === "done" || chunk.type === "error") return;
1237
1685
  }
1686
+ yield { type: "done" };
1687
+ } catch (error) {
1688
+ if (cancelled || controller.signal.aborted || isAbortError(error)) return;
1689
+ const message = error instanceof Error ? error.message : String(error);
1690
+ yield adapterErrorChunk(message, { cause: error });
1238
1691
  }
1239
- if (!endedExplicitly) yield { type: "done" };
1240
1692
  },
1241
1693
  abort: () => {
1242
1694
  cancelled = true;
1695
+ controller.abort();
1243
1696
  }
1244
1697
  };
1245
1698
  }
@@ -1262,22 +1715,43 @@ function resolve(response, callIndex, request) {
1262
1715
  }
1263
1716
  function recordingAdapter(inner, sink) {
1264
1717
  return {
1718
+ capabilities: inner.capabilities,
1265
1719
  createSource: (request) => {
1266
- const innerSource = inner.createSource(request);
1720
+ let innerSource;
1267
1721
  const captured = [];
1268
- const recordedAt = (/* @__PURE__ */ new Date()).toISOString();
1722
+ let aborted = false;
1269
1723
  return {
1270
1724
  stream: async function* () {
1725
+ if (aborted) return;
1726
+ const recordedAt = (/* @__PURE__ */ new Date()).toISOString();
1271
1727
  try {
1728
+ innerSource = inner.createSource(request);
1272
1729
  for await (const chunk of innerSource.stream()) {
1730
+ if (aborted) return;
1273
1731
  captured.push(chunk);
1274
1732
  yield chunk;
1733
+ if (chunk.type === "done" || chunk.type === "error") return;
1275
1734
  }
1735
+ const errorChunk = adapterErrorChunk("Recorded adapter ended without a terminal chunk");
1736
+ captured.push(errorChunk);
1737
+ yield errorChunk;
1738
+ } catch (error) {
1739
+ if (aborted || isAbortError(error)) return;
1740
+ const message = error instanceof Error ? error.message : String(error);
1741
+ const errorChunk = adapterErrorChunk(message, { cause: error });
1742
+ captured.push(errorChunk);
1743
+ yield errorChunk;
1276
1744
  } finally {
1277
- await sink.push({ recordedAt, request, chunks: captured });
1745
+ try {
1746
+ await sink.push({ recordedAt, request, chunks: captured });
1747
+ } catch {
1748
+ }
1278
1749
  }
1279
1750
  },
1280
- abort: () => innerSource.abort()
1751
+ abort: () => {
1752
+ aborted = true;
1753
+ innerSource?.abort();
1754
+ }
1281
1755
  };
1282
1756
  }
1283
1757
  };
@@ -1303,15 +1777,40 @@ function replayAdapter(fixture) {
1303
1777
  response: fixture.map((turn) => turn.chunks)
1304
1778
  });
1305
1779
  }
1306
-
1307
- // src/embedders/shared.ts
1308
- var ERROR_BODY_PREVIEW_CHARS = 200;
1309
- async function throwIfNotOk(response, label, url) {
1780
+ async function throwIfNotOk(response, label, _url) {
1310
1781
  if (response.ok) return;
1311
- const body = await response.text().catch(() => "");
1312
- throw new Error(
1313
- `HTTP ${response.status} from ${label} ${url}: ${body.slice(0, ERROR_BODY_PREVIEW_CHARS)}`
1314
- );
1782
+ void response.body?.cancel().catch(() => {
1783
+ });
1784
+ throw embeddingError(label, `HTTP ${response.status}`);
1785
+ }
1786
+ function embeddingError(label, detail, cause) {
1787
+ return new AdapterError({
1788
+ code: ErrorCodes.AK_ADAPTER_STREAM_FAILED,
1789
+ message: `${label} embedding failed: ${detail}`,
1790
+ cause
1791
+ });
1792
+ }
1793
+ function requireEmbeddingVector(value, label) {
1794
+ if (!Array.isArray(value) || value.length === 0) {
1795
+ throw new AdapterError({
1796
+ code: ErrorCodes.AK_ADAPTER_STREAM_FAILED,
1797
+ message: `${label} returned an empty or missing embedding vector`,
1798
+ hint: "The provider responded 2xx but the embedding payload was empty or malformed."
1799
+ });
1800
+ }
1801
+ const vector = [];
1802
+ for (let i = 0; i < value.length; i++) {
1803
+ const n = value[i];
1804
+ if (typeof n !== "number" || !Number.isFinite(n)) {
1805
+ throw new AdapterError({
1806
+ code: ErrorCodes.AK_ADAPTER_STREAM_FAILED,
1807
+ message: `${label} returned a non-finite embedding value at index ${i}`,
1808
+ hint: "Embedding vectors must be finite numbers only."
1809
+ });
1810
+ }
1811
+ vector.push(n);
1812
+ }
1813
+ return vector;
1315
1814
  }
1316
1815
 
1317
1816
  // src/embedders/openai.ts
@@ -1320,7 +1819,7 @@ async function fetchAvailableModels(baseUrl, apiKey) {
1320
1819
  const response = await fetch(url, {
1321
1820
  headers: { "Authorization": `Bearer ${apiKey}` }
1322
1821
  });
1323
- await throwIfNotOk(response, "openai", url);
1822
+ await throwIfNotOk(response, "openai");
1324
1823
  const data = await response.json();
1325
1824
  return data.data.map((m) => m.id).filter((id) => id.includes("embed")).sort();
1326
1825
  }
@@ -1328,13 +1827,9 @@ async function buildModelError(baseUrl, apiKey, originalError) {
1328
1827
  try {
1329
1828
  const models = await fetchAvailableModels(baseUrl, apiKey);
1330
1829
  const list = models.length > 0 ? models.join(", ") : "none found";
1331
- return new Error(
1332
- `OpenAI embedding failed: ${originalError}. Available embedding models: ${list}`
1333
- );
1830
+ return embeddingError("OpenAI", `${originalError}. Available embedding models: ${list}`);
1334
1831
  } catch (fetchError) {
1335
- return new Error(
1336
- `OpenAI embedding failed: ${originalError}. Could not fetch available models: ${fetchError.message}`
1337
- );
1832
+ return embeddingError("OpenAI", `${originalError}. Could not fetch available models`, fetchError);
1338
1833
  }
1339
1834
  }
1340
1835
  function openaiEmbedder(config) {
@@ -1349,20 +1844,24 @@ function openaiEmbedder(config) {
1349
1844
  body: JSON.stringify({ model, input: text })
1350
1845
  });
1351
1846
  if (!response.ok) {
1352
- const errorBody = await response.json().catch(() => ({}));
1353
- const message = errorBody.error?.message ?? `HTTP ${response.status}`;
1847
+ void response.body?.cancel().catch(() => {
1848
+ });
1849
+ const message = `HTTP ${response.status}`;
1354
1850
  throw await buildModelError(baseUrl, apiKey, message);
1355
1851
  }
1356
1852
  const data = await response.json();
1357
- return data.data[0].embedding;
1853
+ const embedding = data.data?.[0]?.embedding;
1854
+ return requireEmbeddingVector(embedding, "OpenAI");
1358
1855
  };
1359
1856
  }
1360
1857
 
1361
1858
  // src/embedders/gemini.ts
1362
1859
  async function fetchAvailableModels2(baseUrl, apiKey) {
1363
- const url = `${baseUrl}/v1beta/models?key=${apiKey}`;
1364
- const response = await fetch(url);
1365
- await throwIfNotOk(response, "gemini", `${baseUrl}/v1beta/models`);
1860
+ const url = `${baseUrl}/v1beta/models`;
1861
+ const response = await fetch(url, {
1862
+ headers: { "x-goog-api-key": apiKey }
1863
+ });
1864
+ await throwIfNotOk(response, "gemini");
1366
1865
  const data = await response.json();
1367
1866
  return data.models.filter((m) => m.supportedGenerationMethods.includes("embedContent")).map((m) => m.name.replace("models/", "")).sort();
1368
1867
  }
@@ -1370,13 +1869,9 @@ async function buildModelError2(baseUrl, apiKey, originalError) {
1370
1869
  try {
1371
1870
  const models = await fetchAvailableModels2(baseUrl, apiKey);
1372
1871
  const list = models.length > 0 ? models.join(", ") : "none found";
1373
- return new Error(
1374
- `Gemini embedding failed: ${originalError}. Available embedding models: ${list}`
1375
- );
1872
+ return embeddingError("Gemini", `${originalError}. Available embedding models: ${list}`);
1376
1873
  } catch (fetchError) {
1377
- return new Error(
1378
- `Gemini embedding failed: ${originalError}. Could not fetch available models: ${fetchError.message}`
1379
- );
1874
+ return embeddingError("Gemini", `${originalError}. Could not fetch available models`, fetchError);
1380
1875
  }
1381
1876
  }
1382
1877
  function geminiEmbedder(config) {
@@ -1387,10 +1882,13 @@ function geminiEmbedder(config) {
1387
1882
  } = config;
1388
1883
  return async (text) => {
1389
1884
  const response = await fetch(
1390
- `${baseUrl}/v1beta/models/${model}:embedContent?key=${apiKey}`,
1885
+ `${baseUrl}/v1beta/models/${model}:embedContent`,
1391
1886
  {
1392
1887
  method: "POST",
1393
- headers: { "Content-Type": "application/json" },
1888
+ headers: {
1889
+ "Content-Type": "application/json",
1890
+ "x-goog-api-key": apiKey
1891
+ },
1394
1892
  body: JSON.stringify({
1395
1893
  model: `models/${model}`,
1396
1894
  content: { parts: [{ text }] }
@@ -1398,12 +1896,13 @@ function geminiEmbedder(config) {
1398
1896
  }
1399
1897
  );
1400
1898
  if (!response.ok) {
1401
- const errorBody = await response.json().catch(() => ({}));
1402
- const message = errorBody.error?.message ?? `HTTP ${response.status}`;
1899
+ void response.body?.cancel().catch(() => {
1900
+ });
1901
+ const message = `HTTP ${response.status}`;
1403
1902
  throw await buildModelError2(baseUrl, apiKey, message);
1404
1903
  }
1405
1904
  const data = await response.json();
1406
- return data.embedding.values;
1905
+ return requireEmbeddingVector(data.embedding?.values, "Gemini");
1407
1906
  };
1408
1907
  }
1409
1908
 
@@ -1411,7 +1910,7 @@ function geminiEmbedder(config) {
1411
1910
  async function fetchAvailableModels3(baseUrl) {
1412
1911
  const url = `${baseUrl}/api/tags`;
1413
1912
  const response = await fetch(url);
1414
- await throwIfNotOk(response, "ollama", url);
1913
+ await throwIfNotOk(response, "ollama");
1415
1914
  const data = await response.json();
1416
1915
  return data.models.map((m) => m.name).filter((name) => name.includes("embed")).sort();
1417
1916
  }
@@ -1419,13 +1918,9 @@ async function buildModelError3(baseUrl, originalError) {
1419
1918
  try {
1420
1919
  const models = await fetchAvailableModels3(baseUrl);
1421
1920
  const list = models.length > 0 ? models.join(", ") : "none found";
1422
- return new Error(
1423
- `Ollama embedding failed: ${originalError}. Available embedding models: ${list}`
1424
- );
1921
+ return embeddingError("Ollama", `${originalError}. Available embedding models: ${list}`);
1425
1922
  } catch (fetchError) {
1426
- return new Error(
1427
- `Ollama embedding failed: ${originalError}. Could not fetch available models: ${fetchError.message}`
1428
- );
1923
+ return embeddingError("Ollama", `${originalError}. Could not fetch available models`, fetchError);
1429
1924
  }
1430
1925
  }
1431
1926
  function ollamaEmbedder(config) {
@@ -1437,12 +1932,14 @@ function ollamaEmbedder(config) {
1437
1932
  body: JSON.stringify({ model, input: text })
1438
1933
  });
1439
1934
  if (!response.ok) {
1440
- const errorBody = await response.json().catch(() => ({}));
1441
- const message = errorBody.error ?? `HTTP ${response.status}`;
1935
+ void response.body?.cancel().catch(() => {
1936
+ });
1937
+ const message = `HTTP ${response.status}`;
1442
1938
  throw await buildModelError3(baseUrl, message);
1443
1939
  }
1444
1940
  const data = await response.json();
1445
- return data.embeddings[0];
1941
+ const first = Array.isArray(data.embeddings) ? data.embeddings[0] : void 0;
1942
+ return requireEmbeddingVector(first, "Ollama");
1446
1943
  };
1447
1944
  }
1448
1945
  async function fetchAvailableModels4(provider, baseUrl, apiKey) {
@@ -1450,7 +1947,7 @@ async function fetchAvailableModels4(provider, baseUrl, apiKey) {
1450
1947
  const response = await fetch(url, {
1451
1948
  headers: { "Authorization": `Bearer ${apiKey}` }
1452
1949
  });
1453
- await throwIfNotOk(response, provider, url);
1950
+ await throwIfNotOk(response, provider);
1454
1951
  const data = await response.json();
1455
1952
  return data.data.map((m) => m.id).filter((id) => id.includes("embed")).sort();
1456
1953
  }
@@ -1458,13 +1955,9 @@ async function buildModelError4(provider, baseUrl, apiKey, originalError) {
1458
1955
  try {
1459
1956
  const models = await fetchAvailableModels4(provider, baseUrl, apiKey);
1460
1957
  const list = models.length > 0 ? models.join(", ") : "none found";
1461
- return new Error(
1462
- `${provider} embedding failed: ${originalError}. Available embedding models: ${list}`
1463
- );
1958
+ return embeddingError(provider, `${originalError}. Available embedding models: ${list}`);
1464
1959
  } catch (fetchError) {
1465
- return new Error(
1466
- `${provider} embedding failed: ${originalError}. Could not fetch available models: ${fetchError.message}`
1467
- );
1960
+ return embeddingError(provider, `${originalError}. Could not fetch available models`, fetchError);
1468
1961
  }
1469
1962
  }
1470
1963
  function createOpenAICompatibleEmbedder(provider, defaultBaseUrl) {
@@ -1487,12 +1980,14 @@ function createOpenAICompatibleEmbedder(provider, defaultBaseUrl) {
1487
1980
  body: JSON.stringify({ model, input: text })
1488
1981
  });
1489
1982
  if (!response.ok) {
1490
- const errorBody = await response.json().catch(() => ({}));
1491
- const message = errorBody.error?.message ?? `HTTP ${response.status}`;
1983
+ void response.body?.cancel().catch(() => {
1984
+ });
1985
+ const message = `HTTP ${response.status}`;
1492
1986
  throw await buildModelError4(provider, baseUrl, apiKey, message);
1493
1987
  }
1494
1988
  const data = await response.json();
1495
- return data.data[0].embedding;
1989
+ const embedding = data.data?.[0]?.embedding;
1990
+ return requireEmbeddingVector(embedding, provider);
1496
1991
  };
1497
1992
  };
1498
1993
  }