@agentskit/adapters 0.7.1 → 0.9.1

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 CHANGED
@@ -4,8 +4,8 @@ Connect to any LLM provider — and swap between them — without touching your
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@agentskit/adapters?color=blue)](https://www.npmjs.com/package/@agentskit/adapters)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/@agentskit/adapters)](https://www.npmjs.com/package/@agentskit/adapters)
7
- [![bundle size](https://img.shields.io/bundlephobia/minzip/@agentskit/adapters)](https://bundlephobia.com/package/@agentskit/adapters)
8
- [![license](https://img.shields.io/npm/l/@agentskit/adapters)](../../LICENSE)
7
+ [![bundle size](https://img.shields.io/bundlejs/size/@agentskit/adapters?label=bundle)](https://bundlejs.com/?q=@agentskit/adapters)
8
+ [![license](https://img.shields.io/badge/license-MIT-blue.svg)](../../LICENSE)
9
9
  [![stability](https://img.shields.io/badge/stability-stable-brightgreen)](../../docs/STABILITY.md)
10
10
  [![GitHub stars](https://img.shields.io/github/stars/AgentsKit-io/agentskit?style=social)](https://github.com/AgentsKit-io/agentskit)
11
11
 
@@ -14,7 +14,7 @@ Connect to any LLM provider — and swap between them — without touching your
14
14
  ## Why adapters
15
15
 
16
16
  - **Vendor independence** — switch from OpenAI to Anthropic to a local Ollama model by changing one line; your hooks, runtime, and tools stay untouched
17
- - **10+ providers included** — Anthropic, OpenAI, Gemini, Ollama, DeepSeek, Grok, Kimi, LangChain, Vercel AI SDK, and any raw `ReadableStream`
17
+ - **20+ providers included** — Anthropic, OpenAI, Gemini, Ollama, DeepSeek, Grok, Kimi, Mistral, Cohere, Together, Groq, Fireworks, OpenRouter, Hugging Face, LM Studio, vLLM, llama.cpp, LangChain, Vercel AI SDK, and any raw `ReadableStream`
18
18
  - **Embedder functions built in** — the same adapter pattern covers text embeddings, so you can reuse provider config for both chat and RAG
19
19
  - **One-line local AI** — `ollama({ model: 'llama3.1' })` for fully offline agents with no API key required
20
20
 
@@ -57,10 +57,28 @@ const rag = createRAG({
57
57
 
58
58
  ## Features
59
59
 
60
- - Providers: Anthropic, OpenAI, Gemini, Ollama, DeepSeek, Grok, Kimi, LangChain, LangGraph, Vercel AI SDK, generic `ReadableStream`
61
- - Embedders: `openaiEmbedder`, `geminiEmbedder`, `ollamaEmbedder`
60
+ - Providers: Anthropic, OpenAI, Gemini, Ollama, DeepSeek, Grok, Kimi, Mistral, Cohere, Together, Groq, Fireworks, OpenRouter, Hugging Face, LM Studio, vLLM, llama.cpp, LangChain, LangGraph, Vercel AI SDK, generic `ReadableStream`
61
+ - Embedders: `openaiEmbedder`, `geminiEmbedder`, `ollamaEmbedder`, `deepseekEmbedder`, `grokEmbedder`, `kimiEmbedder`, `createOpenAICompatibleEmbedder`
62
62
  - All adapters satisfy `Adapter` contract v1 (ADR 0001) — substitutable anywhere in the ecosystem
63
63
  - Custom adapter authoring via `createAdapter()`
64
+ - Higher-order adapters: `createRouter` (cost/latency/classifier), `createEnsembleAdapter` (fan-out + merge), `createFallbackAdapter` (ordered try-next)
65
+
66
+ ## Higher-order adapters
67
+
68
+ ```ts
69
+ import { createRouter, anthropic, openai } from '@agentskit/adapters'
70
+
71
+ // Auto-pick cheapest capable candidate per request.
72
+ const router = createRouter({
73
+ candidates: [
74
+ { id: 'haiku', adapter: anthropic({ model: 'claude-haiku-4-5' }), cost: 0.25 },
75
+ { id: 'sonnet', adapter: anthropic({ model: 'claude-sonnet-4-6' }), cost: 3 },
76
+ { id: 'gpt-mini', adapter: openai({ model: 'gpt-4o-mini' }), cost: 0.15 },
77
+ ],
78
+ })
79
+ ```
80
+
81
+ See [Adapter router](https://www.agentskit.io/docs/recipes/adapter-router), [Ensemble](https://www.agentskit.io/docs/recipes/adapter-ensemble), and [Fallback chain](https://www.agentskit.io/docs/recipes/fallback-chain).
64
82
 
65
83
  ## Ecosystem
66
84
 
package/dist/index.cjs CHANGED
@@ -777,6 +777,274 @@ function vercelAI(config) {
777
777
  };
778
778
  }
779
779
 
780
+ // src/mistral.ts
781
+ var mistral = createOpenAICompatibleAdapter("https://api.mistral.ai/v1");
782
+
783
+ // src/cohere.ts
784
+ var cohere = createOpenAICompatibleAdapter("https://api.cohere.com/compatibility/v1");
785
+
786
+ // src/together.ts
787
+ var together = createOpenAICompatibleAdapter("https://api.together.xyz/v1");
788
+
789
+ // src/groq.ts
790
+ var groq = createOpenAICompatibleAdapter("https://api.groq.com/openai/v1");
791
+
792
+ // src/fireworks.ts
793
+ var fireworks = createOpenAICompatibleAdapter("https://api.fireworks.ai/inference/v1");
794
+
795
+ // src/openrouter.ts
796
+ var openrouter = createOpenAICompatibleAdapter("https://openrouter.ai/api/v1");
797
+
798
+ // src/huggingface.ts
799
+ var huggingface = createOpenAICompatibleAdapter("https://router.huggingface.co/v1");
800
+
801
+ // src/lmstudio.ts
802
+ var lmstudio = createOpenAICompatibleAdapter("http://localhost:1234/v1");
803
+
804
+ // src/vllm.ts
805
+ var vllm = createOpenAICompatibleAdapter("http://localhost:8000/v1");
806
+
807
+ // src/llamacpp.ts
808
+ var llamacpp = createOpenAICompatibleAdapter("http://localhost:8080/v1");
809
+
810
+ // src/router.ts
811
+ function requireCapabilities(request) {
812
+ const needs = {};
813
+ if (request.context?.tools && request.context.tools.length > 0) needs.tools = true;
814
+ return needs;
815
+ }
816
+ function matchesCapabilities(need, have) {
817
+ if (!have) return true;
818
+ for (const key of Object.keys(need)) {
819
+ if (key === "extensions") continue;
820
+ if (need[key] && have[key] === false) return false;
821
+ }
822
+ return true;
823
+ }
824
+ function pickSyncPolicy(pool, policy) {
825
+ if (policy === "cheapest") {
826
+ const c = pool.reduce((best, x) => (x.cost ?? Infinity) < (best.cost ?? Infinity) ? x : best, pool[0]);
827
+ return { c, reason: "cheapest" };
828
+ }
829
+ if (policy === "fastest") {
830
+ const c = pool.reduce((best, x) => (x.latencyMs ?? Infinity) < (best.latencyMs ?? Infinity) ? x : best, pool[0]);
831
+ return { c, reason: "fastest" };
832
+ }
833
+ return { c: pool[0], reason: "capability-match" };
834
+ }
835
+ function createRouter(options) {
836
+ const { candidates } = options;
837
+ if (candidates.length === 0) throw new Error("createRouter requires at least one candidate");
838
+ const policy = options.policy ?? "cheapest";
839
+ return {
840
+ createSource: (request) => {
841
+ const need = requireCapabilities(request);
842
+ const capable = candidates.filter((c2) => matchesCapabilities(need, c2.capabilities ?? c2.adapter.capabilities));
843
+ const classified = options.classify?.(request);
844
+ if (typeof classified === "string") {
845
+ const c2 = capable.find((x) => x.id === classified);
846
+ if (c2) {
847
+ options.onRoute?.({ id: c2.id, reason: "classify:id", request });
848
+ return c2.adapter.createSource(request);
849
+ }
850
+ }
851
+ let pool = capable;
852
+ let pickedBy;
853
+ if (Array.isArray(classified) && classified.length > 0) {
854
+ const filtered = capable.filter((c2) => c2.tags && classified.every((t) => c2.tags.includes(t)));
855
+ if (filtered.length > 0) {
856
+ pool = filtered;
857
+ pickedBy = "classify:tags";
858
+ }
859
+ }
860
+ if (pool.length === 0) throw new Error("no candidate satisfies the request");
861
+ if (typeof policy === "function") {
862
+ const maybe = policy({ request, candidates: pool });
863
+ if (maybe instanceof Promise) {
864
+ return {
865
+ abort: () => {
866
+ },
867
+ stream: async function* () {
868
+ const id = await maybe;
869
+ const c3 = pool.find((x) => x.id === id);
870
+ if (!c3) throw new Error(`policy returned unknown id: ${id}`);
871
+ options.onRoute?.({ id: c3.id, reason: pickedBy ?? "custom policy", request });
872
+ for await (const chunk of c3.adapter.createSource(request).stream()) yield chunk;
873
+ }
874
+ };
875
+ }
876
+ const c2 = pool.find((x) => x.id === maybe);
877
+ if (!c2) throw new Error(`policy returned unknown id: ${maybe}`);
878
+ options.onRoute?.({ id: c2.id, reason: pickedBy ?? "custom policy", request });
879
+ return c2.adapter.createSource(request);
880
+ }
881
+ const { c, reason } = pickSyncPolicy(pool, policy);
882
+ options.onRoute?.({ id: c.id, reason: pickedBy ?? reason, request });
883
+ return c.adapter.createSource(request);
884
+ }
885
+ };
886
+ }
887
+
888
+ // src/ensemble.ts
889
+ async function drain(source, timeoutMs) {
890
+ const chunks = [];
891
+ let text = "";
892
+ let timer;
893
+ let timedOut = false;
894
+ let error;
895
+ if (timeoutMs !== void 0) {
896
+ timer = setTimeout(() => {
897
+ timedOut = true;
898
+ source.abort();
899
+ }, timeoutMs);
900
+ }
901
+ try {
902
+ for await (const chunk of source.stream()) {
903
+ chunks.push(chunk);
904
+ if (chunk.type === "text" && typeof chunk.content === "string") text += chunk.content;
905
+ }
906
+ } catch (err) {
907
+ error = err instanceof Error ? err : new Error(String(err));
908
+ } finally {
909
+ if (timer) clearTimeout(timer);
910
+ }
911
+ if (timedOut) error = new Error(`timeout after ${timeoutMs}ms`);
912
+ return { chunks, text, error };
913
+ }
914
+ function majorityVote(branches, candidates) {
915
+ const counts = /* @__PURE__ */ new Map();
916
+ const weightById = new Map(candidates.map((c) => [c.id, c.weight ?? 1]));
917
+ for (const b of branches) {
918
+ if (b.error) continue;
919
+ counts.set(b.text, (counts.get(b.text) ?? 0) + (weightById.get(b.id) ?? 1));
920
+ }
921
+ let best = "";
922
+ let bestScore = -1;
923
+ for (const [text, score] of counts) {
924
+ if (score > bestScore) {
925
+ bestScore = score;
926
+ best = text;
927
+ }
928
+ }
929
+ return best;
930
+ }
931
+ function createEnsembleAdapter(options) {
932
+ if (options.candidates.length === 0) {
933
+ throw new Error("createEnsembleAdapter requires at least one candidate");
934
+ }
935
+ const aggregate = options.aggregate ?? "majority-vote";
936
+ return {
937
+ createSource: (request) => {
938
+ let aborted = false;
939
+ const sources = [];
940
+ const run = async () => {
941
+ const tasks = options.candidates.map(async (c, i) => {
942
+ const source = c.adapter.createSource(request);
943
+ sources[i] = source;
944
+ const { chunks, text, error } = await drain(source, options.timeoutMs);
945
+ return { id: c.id, chunks, text, error };
946
+ });
947
+ return Promise.all(tasks);
948
+ };
949
+ const finalText = async (branches) => {
950
+ if (typeof aggregate === "function") return aggregate(branches);
951
+ if (aggregate === "concat") {
952
+ return branches.filter((b) => !b.error).map((b) => b.text).join("\n---\n");
953
+ }
954
+ if (aggregate === "longest") {
955
+ return branches.filter((b) => !b.error).reduce(
956
+ (best, b) => b.text.length > best.length ? b.text : best,
957
+ ""
958
+ );
959
+ }
960
+ return majorityVote(branches, options.candidates);
961
+ };
962
+ return {
963
+ abort: () => {
964
+ aborted = true;
965
+ for (const s of sources) s?.abort();
966
+ },
967
+ stream: async function* () {
968
+ const branches = await run();
969
+ options.onBranches?.(branches);
970
+ if (aborted) return;
971
+ const text = await finalText(branches);
972
+ if (!text && branches.every((b) => b.error)) {
973
+ throw new Error("all ensemble branches failed");
974
+ }
975
+ yield { type: "text", content: text, metadata: { ensemble: branches.map((b) => ({ id: b.id, bytes: b.text.length, error: b.error?.message })) } };
976
+ yield { type: "done" };
977
+ }
978
+ };
979
+ }
980
+ };
981
+ }
982
+
983
+ // src/fallback.ts
984
+ function createFallbackAdapter(candidates, options = {}) {
985
+ if (candidates.length === 0) {
986
+ throw new Error("createFallbackAdapter requires at least one candidate");
987
+ }
988
+ return {
989
+ createSource: (request) => {
990
+ let active;
991
+ let aborted = false;
992
+ return {
993
+ abort: () => {
994
+ aborted = true;
995
+ active?.abort();
996
+ },
997
+ stream: async function* () {
998
+ const errors = [];
999
+ for (let i = 0; i < candidates.length; i++) {
1000
+ if (aborted) return;
1001
+ const candidate = candidates[i];
1002
+ let source;
1003
+ try {
1004
+ source = candidate.adapter.createSource(request);
1005
+ } catch (err) {
1006
+ const error = err instanceof Error ? err : new Error(String(err));
1007
+ errors.push({ id: candidate.id, error });
1008
+ options.onFallback?.({ id: candidate.id, index: i, error });
1009
+ if (options.shouldRetry && !options.shouldRetry(error, i)) throw error;
1010
+ continue;
1011
+ }
1012
+ active = source;
1013
+ const iter = source.stream()[Symbol.asyncIterator]();
1014
+ let first;
1015
+ try {
1016
+ first = await iter.next();
1017
+ } catch (err) {
1018
+ const error = err instanceof Error ? err : new Error(String(err));
1019
+ errors.push({ id: candidate.id, error });
1020
+ options.onFallback?.({ id: candidate.id, index: i, error });
1021
+ if (options.shouldRetry && !options.shouldRetry(error, i)) throw error;
1022
+ active = void 0;
1023
+ continue;
1024
+ }
1025
+ if (first.done) {
1026
+ const error = new Error(`candidate ${candidate.id} emitted no chunks`);
1027
+ errors.push({ id: candidate.id, error });
1028
+ options.onFallback?.({ id: candidate.id, index: i, error });
1029
+ if (options.shouldRetry && !options.shouldRetry(error, i)) throw error;
1030
+ active = void 0;
1031
+ continue;
1032
+ }
1033
+ yield first.value;
1034
+ while (true) {
1035
+ const next = await iter.next();
1036
+ if (next.done) return;
1037
+ yield next.value;
1038
+ }
1039
+ }
1040
+ const summary = errors.map((e) => `${e.id}: ${e.error.message}`).join("; ");
1041
+ throw new Error(`all fallback candidates failed (${summary})`);
1042
+ }
1043
+ };
1044
+ }
1045
+ };
1046
+ }
1047
+
780
1048
  // src/mock.ts
781
1049
  function mockAdapter(options) {
782
1050
  const { response, delayMs = 0, history } = options;
@@ -1065,29 +1333,42 @@ var kimiEmbedder = createOpenAICompatibleEmbedder("Kimi", "https://api.moonshot.
1065
1333
 
1066
1334
  exports.anthropic = anthropic;
1067
1335
  exports.chunkText = chunkText;
1336
+ exports.cohere = cohere;
1068
1337
  exports.createAdapter = createAdapter;
1338
+ exports.createEnsembleAdapter = createEnsembleAdapter;
1339
+ exports.createFallbackAdapter = createFallbackAdapter;
1069
1340
  exports.createOpenAICompatibleEmbedder = createOpenAICompatibleEmbedder;
1341
+ exports.createRouter = createRouter;
1070
1342
  exports.deepseek = deepseek;
1071
1343
  exports.deepseekEmbedder = deepseekEmbedder;
1072
1344
  exports.fetchWithRetry = fetchWithRetry;
1345
+ exports.fireworks = fireworks;
1073
1346
  exports.gemini = gemini;
1074
1347
  exports.geminiEmbedder = geminiEmbedder;
1075
1348
  exports.generic = generic;
1076
1349
  exports.grok = grok;
1077
1350
  exports.grokEmbedder = grokEmbedder;
1351
+ exports.groq = groq;
1352
+ exports.huggingface = huggingface;
1078
1353
  exports.inMemorySink = inMemorySink;
1079
1354
  exports.kimi = kimi;
1080
1355
  exports.kimiEmbedder = kimiEmbedder;
1081
1356
  exports.langchain = langchain;
1082
1357
  exports.langgraph = langgraph;
1358
+ exports.llamacpp = llamacpp;
1359
+ exports.lmstudio = lmstudio;
1360
+ exports.mistral = mistral;
1083
1361
  exports.mockAdapter = mockAdapter;
1084
1362
  exports.ollama = ollama;
1085
1363
  exports.ollamaEmbedder = ollamaEmbedder;
1086
1364
  exports.openai = openai;
1087
1365
  exports.openaiEmbedder = openaiEmbedder;
1366
+ exports.openrouter = openrouter;
1088
1367
  exports.recordingAdapter = recordingAdapter;
1089
1368
  exports.replayAdapter = replayAdapter;
1090
1369
  exports.simulateStream = simulateStream;
1370
+ exports.together = together;
1091
1371
  exports.vercelAI = vercelAI;
1372
+ exports.vllm = vllm;
1092
1373
  //# sourceMappingURL=index.cjs.map
1093
1374
  //# sourceMappingURL=index.cjs.map