@agentskit/adapters 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # @agentskit/adapters
2
+
3
+ Connect to any LLM provider — and swap between them — without touching your app code.
4
+
5
+ ## Why
6
+
7
+ - **Vendor independence** — switch from OpenAI to Anthropic to a local Ollama model by changing one line; your hooks, runtime, and tools stay untouched
8
+ - **10+ providers included** — Anthropic, OpenAI, Gemini, Ollama, DeepSeek, Grok, Kimi, LangChain, Vercel AI SDK, and any raw `ReadableStream`
9
+ - **Embedder functions built in** — the same adapter pattern covers text embeddings, so you can reuse provider config for both chat and RAG
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install @agentskit/adapters
15
+ ```
16
+
17
+ ## Quick example
18
+
19
+ ```ts
20
+ import { anthropic, openai, ollama } from '@agentskit/adapters'
21
+ import { createRuntime } from '@agentskit/runtime'
22
+
23
+ // Switch provider by swapping one import
24
+ const adapter = anthropic({ apiKey: process.env.ANTHROPIC_API_KEY, model: 'claude-sonnet-4-6' })
25
+ // const adapter = openai({ apiKey: process.env.OPENAI_API_KEY, model: 'gpt-4o' })
26
+ // const adapter = ollama({ model: 'llama3.1' })
27
+
28
+ const runtime = createRuntime({ adapter })
29
+ const result = await runtime.run('Summarize the latest AI news')
30
+ console.log(result.content)
31
+ ```
32
+
33
+ ## Embeddings (for RAG)
34
+
35
+ Use the same package for vector embeddings — wire `openaiEmbedder`, `geminiEmbedder`, or `ollamaEmbedder` into [`@agentskit/rag`](https://www.npmjs.com/package/@agentskit/rag):
36
+
37
+ ```ts
38
+ import { openaiEmbedder } from '@agentskit/adapters'
39
+ import { createRAG } from '@agentskit/rag'
40
+ import { fileVectorMemory } from '@agentskit/memory'
41
+
42
+ const rag = createRAG({
43
+ embed: openaiEmbedder({ apiKey: process.env.OPENAI_API_KEY! }),
44
+ store: fileVectorMemory({ path: './vectors' }),
45
+ })
46
+ ```
47
+
48
+ ## Next steps
49
+
50
+ - Pair adapters with [`@agentskit/runtime`](https://www.npmjs.com/package/@agentskit/runtime), [`@agentskit/react`](https://www.npmjs.com/package/@agentskit/react), or [`@agentskit/ink`](https://www.npmjs.com/package/@agentskit/ink) — the adapter instance is the only provider-specific piece
51
+
52
+ ## Ecosystem
53
+
54
+ | Package | Role |
55
+ |---------|------|
56
+ | [@agentskit/core](https://www.npmjs.com/package/@agentskit/core) | `Adapter`, `EmbedFn`, types |
57
+ | [@agentskit/runtime](https://www.npmjs.com/package/@agentskit/runtime) | Headless `createRuntime` |
58
+ | [@agentskit/rag](https://www.npmjs.com/package/@agentskit/rag) | `createRAG` + embedders |
59
+ | [@agentskit/memory](https://www.npmjs.com/package/@agentskit/memory) | Vector + chat memory backends |
60
+
61
+ ## Docs
62
+
63
+ [Full documentation](https://emersonbraun.github.io/agentskit/)
package/dist/index.cjs CHANGED
@@ -107,8 +107,13 @@ async function* readNDJSONLines(stream) {
107
107
  }
108
108
  }
109
109
  async function* parseOpenAIStream(stream) {
110
+ const pendingToolCalls = /* @__PURE__ */ new Map();
110
111
  for await (const data of readSSELines(stream)) {
111
112
  if (data === "[DONE]") {
113
+ for (const [, tc] of pendingToolCalls) {
114
+ yield { type: "tool_call", toolCall: { id: tc.id, name: tc.name, args: tc.args || "{}" } };
115
+ }
116
+ pendingToolCalls.clear();
112
117
  yield { type: "done" };
113
118
  return;
114
119
  }
@@ -117,56 +122,104 @@ async function* parseOpenAIStream(stream) {
117
122
  const delta = event.choices?.[0]?.delta;
118
123
  if (typeof delta?.content === "string") {
119
124
  yield { type: "text", content: delta.content };
120
- } else if (Array.isArray(delta?.tool_calls)) {
125
+ }
126
+ if (Array.isArray(delta?.tool_calls)) {
121
127
  for (const toolCall of delta.tool_calls) {
128
+ const index = toolCall.index ?? 0;
129
+ const existing = pendingToolCalls.get(index);
122
130
  if (toolCall?.function?.name) {
123
- yield {
124
- type: "tool_call",
125
- toolCall: {
126
- id: toolCall.id ?? `${toolCall.function.name}-${Date.now()}`,
127
- name: toolCall.function.name,
128
- args: toolCall.function.arguments ?? "{}"
129
- }
130
- };
131
+ pendingToolCalls.set(index, {
132
+ id: toolCall.id ?? existing?.id ?? `tool-${index}-${Date.now()}`,
133
+ name: toolCall.function.name,
134
+ args: (existing?.args ?? "") + (toolCall.function.arguments ?? "")
135
+ });
136
+ } else if (existing && toolCall?.function?.arguments) {
137
+ existing.args += toolCall.function.arguments;
131
138
  }
132
139
  }
133
140
  }
134
141
  } catch {
135
142
  }
136
143
  }
144
+ for (const [, tc] of pendingToolCalls) {
145
+ yield { type: "tool_call", toolCall: { id: tc.id, name: tc.name, args: tc.args || "{}" } };
146
+ }
147
+ pendingToolCalls.clear();
137
148
  yield { type: "done" };
138
149
  }
139
150
  async function* parseAnthropicStream(stream) {
151
+ const pendingToolCalls = /* @__PURE__ */ new Map();
140
152
  for await (const data of readSSELines(stream)) {
141
153
  if (data === "[DONE]") continue;
142
154
  try {
143
155
  const event = JSON.parse(data);
144
- if (event.type === "content_block_delta" && event.delta?.text) {
156
+ if (event.type === "content_block_delta" && event.delta?.type === "text_delta" && event.delta?.text) {
145
157
  yield { type: "text", content: event.delta.text };
158
+ } else if (event.type === "content_block_delta" && event.delta?.type === "input_json_delta") {
159
+ const index = event.index ?? 0;
160
+ const existing = pendingToolCalls.get(index);
161
+ if (existing) {
162
+ existing.args += event.delta.partial_json ?? "";
163
+ }
146
164
  } else if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
147
- yield {
148
- type: "tool_call",
149
- toolCall: {
150
- id: event.content_block.id,
151
- name: event.content_block.name,
152
- args: JSON.stringify(event.content_block.input ?? {})
153
- }
154
- };
165
+ const index = event.index ?? 0;
166
+ pendingToolCalls.set(index, {
167
+ id: event.content_block.id,
168
+ name: event.content_block.name,
169
+ args: ""
170
+ });
171
+ } else if (event.type === "content_block_stop") {
172
+ const index = event.index ?? 0;
173
+ const tc = pendingToolCalls.get(index);
174
+ if (tc) {
175
+ yield {
176
+ type: "tool_call",
177
+ toolCall: {
178
+ id: tc.id,
179
+ name: tc.name,
180
+ args: tc.args || "{}"
181
+ }
182
+ };
183
+ pendingToolCalls.delete(index);
184
+ }
155
185
  } else if (event.type === "message_stop") {
186
+ for (const [, tc] of pendingToolCalls) {
187
+ yield { type: "tool_call", toolCall: { id: tc.id, name: tc.name, args: tc.args || "{}" } };
188
+ }
189
+ pendingToolCalls.clear();
156
190
  yield { type: "done" };
157
191
  return;
158
192
  }
159
193
  } catch {
160
194
  }
161
195
  }
196
+ for (const [, tc] of pendingToolCalls) {
197
+ yield { type: "tool_call", toolCall: { id: tc.id, name: tc.name, args: tc.args || "{}" } };
198
+ }
199
+ pendingToolCalls.clear();
162
200
  yield { type: "done" };
163
201
  }
164
202
  async function* parseGeminiStream(stream) {
165
203
  for await (const data of readSSELines(stream)) {
166
204
  try {
167
205
  const event = JSON.parse(data);
168
- const text = event.candidates?.[0]?.content?.parts?.map((part) => part.text ?? "").join("");
169
- if (text) yield { type: "text", content: text };
206
+ const parts = event.candidates?.[0]?.content?.parts;
207
+ if (!Array.isArray(parts)) continue;
208
+ for (const part of parts) {
209
+ if (typeof part.text === "string" && part.text) {
210
+ yield { type: "text", content: part.text };
211
+ } else if (part.functionCall) {
212
+ const fc = part.functionCall;
213
+ yield {
214
+ type: "tool_call",
215
+ toolCall: {
216
+ id: fc.id ?? `${fc.name}-${Date.now()}`,
217
+ name: fc.name,
218
+ args: JSON.stringify(fc.args ?? {})
219
+ }
220
+ };
221
+ }
222
+ }
170
223
  } catch {
171
224
  }
172
225
  }
@@ -179,6 +232,20 @@ async function* parseOllamaStream(stream) {
179
232
  if (event.message?.content) {
180
233
  yield { type: "text", content: event.message.content };
181
234
  }
235
+ if (Array.isArray(event.message?.tool_calls)) {
236
+ for (const tc of event.message.tool_calls) {
237
+ if (tc?.function?.name) {
238
+ yield {
239
+ type: "tool_call",
240
+ toolCall: {
241
+ id: tc.id ?? `${tc.function.name}-${Date.now()}`,
242
+ name: tc.function.name,
243
+ args: typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments ?? {})
244
+ }
245
+ };
246
+ }
247
+ }
248
+ }
182
249
  if (event.done) {
183
250
  yield { type: "done" };
184
251
  return;
@@ -471,17 +538,218 @@ function vercelAI(config) {
471
538
  };
472
539
  }
473
540
 
541
+ // src/embedders/openai.ts
542
+ async function fetchAvailableModels(baseUrl, apiKey) {
543
+ const response = await fetch(`${baseUrl}/v1/models`, {
544
+ headers: { "Authorization": `Bearer ${apiKey}` }
545
+ });
546
+ if (!response.ok) {
547
+ throw new Error(`HTTP ${response.status}`);
548
+ }
549
+ const data = await response.json();
550
+ return data.data.map((m) => m.id).filter((id) => id.includes("embed")).sort();
551
+ }
552
+ async function buildModelError(baseUrl, apiKey, originalError) {
553
+ try {
554
+ const models = await fetchAvailableModels(baseUrl, apiKey);
555
+ const list = models.length > 0 ? models.join(", ") : "none found";
556
+ return new Error(
557
+ `OpenAI embedding failed: ${originalError}. Available embedding models: ${list}`
558
+ );
559
+ } catch (fetchError) {
560
+ return new Error(
561
+ `OpenAI embedding failed: ${originalError}. Could not fetch available models: ${fetchError.message}`
562
+ );
563
+ }
564
+ }
565
+ function openaiEmbedder(config) {
566
+ const { apiKey, model = "text-embedding-3-small", baseUrl = "https://api.openai.com" } = config;
567
+ return async (text) => {
568
+ const response = await fetch(`${baseUrl}/v1/embeddings`, {
569
+ method: "POST",
570
+ headers: {
571
+ "Content-Type": "application/json",
572
+ "Authorization": `Bearer ${apiKey}`
573
+ },
574
+ body: JSON.stringify({ model, input: text })
575
+ });
576
+ if (!response.ok) {
577
+ const errorBody = await response.json().catch(() => ({}));
578
+ const message = errorBody.error?.message ?? `HTTP ${response.status}`;
579
+ throw await buildModelError(baseUrl, apiKey, message);
580
+ }
581
+ const data = await response.json();
582
+ return data.data[0].embedding;
583
+ };
584
+ }
585
+
586
+ // src/embedders/gemini.ts
587
+ async function fetchAvailableModels2(baseUrl, apiKey) {
588
+ const response = await fetch(`${baseUrl}/v1beta/models?key=${apiKey}`);
589
+ if (!response.ok) {
590
+ throw new Error(`HTTP ${response.status}`);
591
+ }
592
+ const data = await response.json();
593
+ return data.models.filter((m) => m.supportedGenerationMethods.includes("embedContent")).map((m) => m.name.replace("models/", "")).sort();
594
+ }
595
+ async function buildModelError2(baseUrl, apiKey, originalError) {
596
+ try {
597
+ const models = await fetchAvailableModels2(baseUrl, apiKey);
598
+ const list = models.length > 0 ? models.join(", ") : "none found";
599
+ return new Error(
600
+ `Gemini embedding failed: ${originalError}. Available embedding models: ${list}`
601
+ );
602
+ } catch (fetchError) {
603
+ return new Error(
604
+ `Gemini embedding failed: ${originalError}. Could not fetch available models: ${fetchError.message}`
605
+ );
606
+ }
607
+ }
608
+ function geminiEmbedder(config) {
609
+ const {
610
+ apiKey,
611
+ model = "text-embedding-004",
612
+ baseUrl = "https://generativelanguage.googleapis.com"
613
+ } = config;
614
+ return async (text) => {
615
+ const response = await fetch(
616
+ `${baseUrl}/v1beta/models/${model}:embedContent?key=${apiKey}`,
617
+ {
618
+ method: "POST",
619
+ headers: { "Content-Type": "application/json" },
620
+ body: JSON.stringify({
621
+ model: `models/${model}`,
622
+ content: { parts: [{ text }] }
623
+ })
624
+ }
625
+ );
626
+ if (!response.ok) {
627
+ const errorBody = await response.json().catch(() => ({}));
628
+ const message = errorBody.error?.message ?? `HTTP ${response.status}`;
629
+ throw await buildModelError2(baseUrl, apiKey, message);
630
+ }
631
+ const data = await response.json();
632
+ return data.embedding.values;
633
+ };
634
+ }
635
+
636
+ // src/embedders/ollama.ts
637
+ async function fetchAvailableModels3(baseUrl) {
638
+ const response = await fetch(`${baseUrl}/api/tags`);
639
+ if (!response.ok) {
640
+ throw new Error(`HTTP ${response.status}`);
641
+ }
642
+ const data = await response.json();
643
+ return data.models.map((m) => m.name).filter((name) => name.includes("embed")).sort();
644
+ }
645
+ async function buildModelError3(baseUrl, originalError) {
646
+ try {
647
+ const models = await fetchAvailableModels3(baseUrl);
648
+ const list = models.length > 0 ? models.join(", ") : "none found";
649
+ return new Error(
650
+ `Ollama embedding failed: ${originalError}. Available embedding models: ${list}`
651
+ );
652
+ } catch (fetchError) {
653
+ return new Error(
654
+ `Ollama embedding failed: ${originalError}. Could not fetch available models: ${fetchError.message}`
655
+ );
656
+ }
657
+ }
658
+ function ollamaEmbedder(config) {
659
+ const { model = "nomic-embed-text", baseUrl = "http://localhost:11434" } = config;
660
+ return async (text) => {
661
+ const response = await fetch(`${baseUrl}/api/embed`, {
662
+ method: "POST",
663
+ headers: { "Content-Type": "application/json" },
664
+ body: JSON.stringify({ model, input: text })
665
+ });
666
+ if (!response.ok) {
667
+ const errorBody = await response.json().catch(() => ({}));
668
+ const message = errorBody.error ?? `HTTP ${response.status}`;
669
+ throw await buildModelError3(baseUrl, message);
670
+ }
671
+ const data = await response.json();
672
+ return data.embeddings[0];
673
+ };
674
+ }
675
+
676
+ // src/embedders/openai-compatible.ts
677
+ async function fetchAvailableModels4(baseUrl, apiKey) {
678
+ const response = await fetch(`${baseUrl}/v1/models`, {
679
+ headers: { "Authorization": `Bearer ${apiKey}` }
680
+ });
681
+ if (!response.ok) {
682
+ throw new Error(`HTTP ${response.status}`);
683
+ }
684
+ const data = await response.json();
685
+ return data.data.map((m) => m.id).filter((id) => id.includes("embed")).sort();
686
+ }
687
+ async function buildModelError4(provider, baseUrl, apiKey, originalError) {
688
+ try {
689
+ const models = await fetchAvailableModels4(baseUrl, apiKey);
690
+ const list = models.length > 0 ? models.join(", ") : "none found";
691
+ return new Error(
692
+ `${provider} embedding failed: ${originalError}. Available embedding models: ${list}`
693
+ );
694
+ } catch (fetchError) {
695
+ return new Error(
696
+ `${provider} embedding failed: ${originalError}. Could not fetch available models: ${fetchError.message}`
697
+ );
698
+ }
699
+ }
700
+ function createOpenAICompatibleEmbedder(provider, defaultBaseUrl) {
701
+ return function embedder(config) {
702
+ if (!config.model) {
703
+ throw new Error(`Model is required for ${provider}. Pass { model: "<model-name>" }.`);
704
+ }
705
+ const { apiKey, model, baseUrl = defaultBaseUrl } = config;
706
+ return async (text) => {
707
+ const response = await fetch(`${baseUrl}/v1/embeddings`, {
708
+ method: "POST",
709
+ headers: {
710
+ "Content-Type": "application/json",
711
+ "Authorization": `Bearer ${apiKey}`
712
+ },
713
+ body: JSON.stringify({ model, input: text })
714
+ });
715
+ if (!response.ok) {
716
+ const errorBody = await response.json().catch(() => ({}));
717
+ const message = errorBody.error?.message ?? `HTTP ${response.status}`;
718
+ throw await buildModelError4(provider, baseUrl, apiKey, message);
719
+ }
720
+ const data = await response.json();
721
+ return data.data[0].embedding;
722
+ };
723
+ };
724
+ }
725
+
726
+ // src/embedders/deepseek.ts
727
+ var deepseekEmbedder = createOpenAICompatibleEmbedder("DeepSeek", "https://api.deepseek.com");
728
+
729
+ // src/embedders/grok.ts
730
+ var grokEmbedder = createOpenAICompatibleEmbedder("Grok", "https://api.x.ai");
731
+
732
+ // src/embedders/kimi.ts
733
+ var kimiEmbedder = createOpenAICompatibleEmbedder("Kimi", "https://api.moonshot.ai");
734
+
474
735
  exports.anthropic = anthropic;
475
736
  exports.createAdapter = createAdapter;
737
+ exports.createOpenAICompatibleEmbedder = createOpenAICompatibleEmbedder;
476
738
  exports.deepseek = deepseek;
739
+ exports.deepseekEmbedder = deepseekEmbedder;
477
740
  exports.gemini = gemini;
741
+ exports.geminiEmbedder = geminiEmbedder;
478
742
  exports.generic = generic;
479
743
  exports.grok = grok;
744
+ exports.grokEmbedder = grokEmbedder;
480
745
  exports.kimi = kimi;
746
+ exports.kimiEmbedder = kimiEmbedder;
481
747
  exports.langchain = langchain;
482
748
  exports.langgraph = langgraph;
483
749
  exports.ollama = ollama;
750
+ exports.ollamaEmbedder = ollamaEmbedder;
484
751
  exports.openai = openai;
752
+ exports.openaiEmbedder = openaiEmbedder;
485
753
  exports.vercelAI = vercelAI;
486
754
  //# sourceMappingURL=index.cjs.map
487
755
  //# sourceMappingURL=index.cjs.map