@agentskit/adapters 0.4.2 → 0.5.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/README.md +2 -0
- package/dist/index.cjs +269 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +150 -2
- package/dist/index.d.ts +150 -2
- package/dist/index.js +263 -13
- package/dist/index.js.map +1 -1
- package/package.json +10 -5
package/README.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -255,12 +255,73 @@ async function* parseOllamaStream(stream) {
|
|
|
255
255
|
}
|
|
256
256
|
yield { type: "done" };
|
|
257
257
|
}
|
|
258
|
-
|
|
258
|
+
var DEFAULT_RETRY = {
|
|
259
|
+
maxAttempts: 3,
|
|
260
|
+
baseDelayMs: 500,
|
|
261
|
+
maxDelayMs: 8e3,
|
|
262
|
+
jitter: true,
|
|
263
|
+
retryOn: ({ error, response }) => {
|
|
264
|
+
if (response) {
|
|
265
|
+
return [408, 429, 500, 502, 503, 504].includes(response.status);
|
|
266
|
+
}
|
|
267
|
+
if (error instanceof DOMException && error.name === "AbortError") return false;
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
function defaultSleep(ms) {
|
|
272
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
273
|
+
}
|
|
274
|
+
function computeDelay(attempt, opts) {
|
|
275
|
+
const exp = Math.min(opts.maxDelayMs, opts.baseDelayMs * Math.pow(2, attempt - 1));
|
|
276
|
+
if (!opts.jitter) return exp;
|
|
277
|
+
return Math.floor(Math.random() * exp);
|
|
278
|
+
}
|
|
279
|
+
function parseRetryAfter(value) {
|
|
280
|
+
if (!value) return void 0;
|
|
281
|
+
const n = Number(value);
|
|
282
|
+
if (!Number.isNaN(n)) return n * 1e3;
|
|
283
|
+
const date = Date.parse(value);
|
|
284
|
+
if (!Number.isNaN(date)) return Math.max(0, date - Date.now());
|
|
285
|
+
return void 0;
|
|
286
|
+
}
|
|
287
|
+
async function fetchWithRetry(doFetch, signal, retryOpt = {}) {
|
|
288
|
+
const opts = {
|
|
289
|
+
...DEFAULT_RETRY,
|
|
290
|
+
...retryOpt
|
|
291
|
+
};
|
|
292
|
+
const sleep = retryOpt.sleep ?? defaultSleep;
|
|
293
|
+
let lastError;
|
|
294
|
+
for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
|
|
295
|
+
if (signal.aborted) throw new DOMException("Aborted", "AbortError");
|
|
296
|
+
try {
|
|
297
|
+
const response = await doFetch(signal);
|
|
298
|
+
if (response.ok) return response;
|
|
299
|
+
if (attempt >= opts.maxAttempts || !opts.retryOn({ response, attempt })) {
|
|
300
|
+
return response;
|
|
301
|
+
}
|
|
302
|
+
const retryAfterMs = parseRetryAfter(response.headers.get("retry-after"));
|
|
303
|
+
const delay = retryAfterMs ?? computeDelay(attempt, opts);
|
|
304
|
+
retryOpt.onRetry?.({ attempt, delayMs: delay, reason: `HTTP ${response.status}` });
|
|
305
|
+
await sleep(delay);
|
|
306
|
+
response.body?.cancel().catch(() => {
|
|
307
|
+
});
|
|
308
|
+
} catch (err) {
|
|
309
|
+
lastError = err;
|
|
310
|
+
if (err instanceof DOMException && err.name === "AbortError") throw err;
|
|
311
|
+
if (attempt >= opts.maxAttempts || !opts.retryOn({ error: err, attempt })) throw err;
|
|
312
|
+
const delay = computeDelay(attempt, opts);
|
|
313
|
+
retryOpt.onRetry?.({ attempt, delayMs: delay, reason: err.message });
|
|
314
|
+
await sleep(delay);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
throw lastError ?? new Error("fetchWithRetry: exhausted attempts");
|
|
318
|
+
}
|
|
319
|
+
function createStreamSource(doFetch, parse, errorLabel, retry) {
|
|
259
320
|
let abortController = new AbortController();
|
|
260
321
|
return {
|
|
261
322
|
stream: async function* () {
|
|
262
323
|
try {
|
|
263
|
-
const response = await doFetch
|
|
324
|
+
const response = await fetchWithRetry(doFetch, abortController.signal, retry ?? {});
|
|
264
325
|
if (!response.ok || !response.body) {
|
|
265
326
|
yield { type: "error", content: `${errorLabel} error: ${response.status}` };
|
|
266
327
|
return;
|
|
@@ -280,11 +341,78 @@ function createStreamSource(doFetch, parse, errorLabel) {
|
|
|
280
341
|
}
|
|
281
342
|
};
|
|
282
343
|
}
|
|
344
|
+
function chunkText(text, targetSize = 32) {
|
|
345
|
+
if (text.length <= targetSize) return [text];
|
|
346
|
+
const out = [];
|
|
347
|
+
let i = 0;
|
|
348
|
+
while (i < text.length) {
|
|
349
|
+
let end = Math.min(text.length, i + targetSize);
|
|
350
|
+
if (end < text.length) {
|
|
351
|
+
const nextSpace = text.indexOf(" ", end);
|
|
352
|
+
if (nextSpace !== -1 && nextSpace - end <= 8) end = nextSpace + 1;
|
|
353
|
+
}
|
|
354
|
+
out.push(text.slice(i, end));
|
|
355
|
+
i = end;
|
|
356
|
+
}
|
|
357
|
+
return out;
|
|
358
|
+
}
|
|
359
|
+
function simulateStream(doFetch, extractText, errorLabel, options = {}) {
|
|
360
|
+
const { chunkSize = 32, delayMs = 8, retry } = options;
|
|
361
|
+
let abortController = new AbortController();
|
|
362
|
+
return {
|
|
363
|
+
stream: async function* () {
|
|
364
|
+
try {
|
|
365
|
+
const response = await fetchWithRetry(doFetch, abortController.signal, retry ?? {});
|
|
366
|
+
if (!response.ok) {
|
|
367
|
+
yield { type: "error", content: `${errorLabel} error: ${response.status}` };
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const text = await extractText(response);
|
|
371
|
+
const chunks = chunkText(text, chunkSize);
|
|
372
|
+
for (const chunk of chunks) {
|
|
373
|
+
if (abortController === null) return;
|
|
374
|
+
if (delayMs > 0) {
|
|
375
|
+
await new Promise((resolve2, reject) => {
|
|
376
|
+
const timer = setTimeout(resolve2, delayMs);
|
|
377
|
+
abortController?.signal.addEventListener(
|
|
378
|
+
"abort",
|
|
379
|
+
() => {
|
|
380
|
+
clearTimeout(timer);
|
|
381
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
382
|
+
},
|
|
383
|
+
{ once: true }
|
|
384
|
+
);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
yield { type: "text", content: chunk };
|
|
388
|
+
}
|
|
389
|
+
yield { type: "done" };
|
|
390
|
+
} catch (err) {
|
|
391
|
+
if (err instanceof DOMException && err.name === "AbortError") return;
|
|
392
|
+
yield {
|
|
393
|
+
type: "error",
|
|
394
|
+
content: err instanceof Error ? err.message : String(err)
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
abort: () => {
|
|
399
|
+
abortController?.abort();
|
|
400
|
+
abortController = null;
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
}
|
|
283
404
|
|
|
284
405
|
// src/anthropic.ts
|
|
285
406
|
function anthropic(config) {
|
|
286
|
-
const { apiKey, model, baseUrl = "https://api.anthropic.com", maxTokens = 4096 } = config;
|
|
407
|
+
const { apiKey, model, baseUrl = "https://api.anthropic.com", maxTokens = 4096, retry } = config;
|
|
287
408
|
return {
|
|
409
|
+
capabilities: {
|
|
410
|
+
streaming: true,
|
|
411
|
+
tools: true,
|
|
412
|
+
reasoning: model.includes("sonnet") || model.includes("opus"),
|
|
413
|
+
multiModal: true,
|
|
414
|
+
usage: true
|
|
415
|
+
},
|
|
288
416
|
createSource: (request) => {
|
|
289
417
|
const body = {
|
|
290
418
|
model,
|
|
@@ -310,7 +438,8 @@ function anthropic(config) {
|
|
|
310
438
|
signal
|
|
311
439
|
}),
|
|
312
440
|
parseAnthropicStream,
|
|
313
|
-
"Anthropic API"
|
|
441
|
+
"Anthropic API",
|
|
442
|
+
retry
|
|
314
443
|
);
|
|
315
444
|
}
|
|
316
445
|
};
|
|
@@ -318,8 +447,17 @@ function anthropic(config) {
|
|
|
318
447
|
|
|
319
448
|
// src/openai.ts
|
|
320
449
|
function openai(config) {
|
|
321
|
-
const { apiKey, model, baseUrl = "https://api.openai.com" } = config;
|
|
450
|
+
const { apiKey, model, baseUrl = "https://api.openai.com", retry } = config;
|
|
322
451
|
return {
|
|
452
|
+
capabilities: {
|
|
453
|
+
streaming: true,
|
|
454
|
+
tools: true,
|
|
455
|
+
// o1 / o3 models emit reasoning; older models don't. Accurate per-model
|
|
456
|
+
// detection would need a model registry; 'true' is the safer default here.
|
|
457
|
+
reasoning: model.startsWith("o1") || model.startsWith("o3"),
|
|
458
|
+
multiModal: model.startsWith("gpt-4") || model.startsWith("o"),
|
|
459
|
+
usage: true
|
|
460
|
+
},
|
|
323
461
|
createSource: (request) => {
|
|
324
462
|
const body = {
|
|
325
463
|
model,
|
|
@@ -347,7 +485,8 @@ function openai(config) {
|
|
|
347
485
|
signal
|
|
348
486
|
}),
|
|
349
487
|
parseOpenAIStream,
|
|
350
|
-
"OpenAI API"
|
|
488
|
+
"OpenAI API",
|
|
489
|
+
retry
|
|
351
490
|
);
|
|
352
491
|
}
|
|
353
492
|
};
|
|
@@ -355,8 +494,14 @@ function openai(config) {
|
|
|
355
494
|
|
|
356
495
|
// src/gemini.ts
|
|
357
496
|
function gemini(config) {
|
|
358
|
-
const { apiKey, model, baseUrl = "https://generativelanguage.googleapis.com" } = config;
|
|
497
|
+
const { apiKey, model, baseUrl = "https://generativelanguage.googleapis.com", retry } = config;
|
|
359
498
|
return {
|
|
499
|
+
capabilities: {
|
|
500
|
+
streaming: true,
|
|
501
|
+
tools: true,
|
|
502
|
+
multiModal: true,
|
|
503
|
+
usage: true
|
|
504
|
+
},
|
|
360
505
|
createSource: (request) => {
|
|
361
506
|
const body = {
|
|
362
507
|
contents: request.messages.filter((message) => message.role !== "system").map((message) => ({
|
|
@@ -379,7 +524,8 @@ function gemini(config) {
|
|
|
379
524
|
}
|
|
380
525
|
),
|
|
381
526
|
parseGeminiStream,
|
|
382
|
-
"Gemini API"
|
|
527
|
+
"Gemini API",
|
|
528
|
+
retry
|
|
383
529
|
);
|
|
384
530
|
}
|
|
385
531
|
};
|
|
@@ -400,8 +546,14 @@ var grok = createOpenAICompatibleAdapter("https://api.x.ai");
|
|
|
400
546
|
|
|
401
547
|
// src/ollama.ts
|
|
402
548
|
function ollama(config) {
|
|
403
|
-
const { model, baseUrl = "http://localhost:11434" } = config;
|
|
549
|
+
const { model, baseUrl = "http://localhost:11434", retry } = config;
|
|
404
550
|
return {
|
|
551
|
+
capabilities: {
|
|
552
|
+
streaming: true,
|
|
553
|
+
tools: false,
|
|
554
|
+
// varies by model; default 'false' — enable via capabilities.extensions if your model supports it
|
|
555
|
+
multiModal: model.includes("llava") || model.includes("vision")
|
|
556
|
+
},
|
|
405
557
|
createSource: (request) => {
|
|
406
558
|
const body = {
|
|
407
559
|
model,
|
|
@@ -419,7 +571,8 @@ function ollama(config) {
|
|
|
419
571
|
signal
|
|
420
572
|
}),
|
|
421
573
|
parseOllamaStream,
|
|
422
|
-
"Ollama API"
|
|
574
|
+
"Ollama API",
|
|
575
|
+
retry
|
|
423
576
|
);
|
|
424
577
|
}
|
|
425
578
|
};
|
|
@@ -516,8 +669,12 @@ async function* parseVercelStream(stream) {
|
|
|
516
669
|
yield { type: "done" };
|
|
517
670
|
}
|
|
518
671
|
function vercelAI(config) {
|
|
519
|
-
const { api, headers = {} } = config;
|
|
672
|
+
const { api, headers = {}, retry } = config;
|
|
520
673
|
return {
|
|
674
|
+
capabilities: {
|
|
675
|
+
// Vercel AI routes can do any of these — whether they do depends on
|
|
676
|
+
// your route handler. Omit the field instead of lying.
|
|
677
|
+
},
|
|
521
678
|
createSource: (request) => {
|
|
522
679
|
const body = {
|
|
523
680
|
messages: request.messages.map((message) => ({ role: message.role, content: message.content })),
|
|
@@ -532,12 +689,105 @@ function vercelAI(config) {
|
|
|
532
689
|
signal
|
|
533
690
|
}),
|
|
534
691
|
parseVercelStream,
|
|
535
|
-
"API"
|
|
692
|
+
"API",
|
|
693
|
+
retry
|
|
536
694
|
);
|
|
537
695
|
}
|
|
538
696
|
};
|
|
539
697
|
}
|
|
540
698
|
|
|
699
|
+
// src/mock.ts
|
|
700
|
+
function mockAdapter(options) {
|
|
701
|
+
const { response, delayMs = 0, history } = options;
|
|
702
|
+
let callIndex = 0;
|
|
703
|
+
return {
|
|
704
|
+
capabilities: {
|
|
705
|
+
streaming: true,
|
|
706
|
+
tools: true,
|
|
707
|
+
reasoning: true,
|
|
708
|
+
multiModal: true,
|
|
709
|
+
usage: true
|
|
710
|
+
},
|
|
711
|
+
createSource: (request) => {
|
|
712
|
+
history?.push(request);
|
|
713
|
+
const myCall = callIndex++;
|
|
714
|
+
let cancelled = false;
|
|
715
|
+
return {
|
|
716
|
+
stream: async function* () {
|
|
717
|
+
const chunks = resolve(response, myCall, request);
|
|
718
|
+
let endedExplicitly = false;
|
|
719
|
+
for (const chunk of chunks) {
|
|
720
|
+
if (cancelled) return;
|
|
721
|
+
if (delayMs > 0) await new Promise((r) => setTimeout(r, delayMs));
|
|
722
|
+
yield chunk;
|
|
723
|
+
if (chunk.type === "done" || chunk.type === "error") {
|
|
724
|
+
endedExplicitly = true;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (!endedExplicitly) yield { type: "done" };
|
|
728
|
+
},
|
|
729
|
+
abort: () => {
|
|
730
|
+
cancelled = true;
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
function resolve(response, callIndex, request) {
|
|
737
|
+
if (Array.isArray(response) && response.length > 0 && Array.isArray(response[0])) {
|
|
738
|
+
const sequence = response;
|
|
739
|
+
const item = sequence[callIndex % sequence.length];
|
|
740
|
+
return item;
|
|
741
|
+
}
|
|
742
|
+
if (Array.isArray(response) && response.length > 0 && typeof response[0] === "function") {
|
|
743
|
+
const sequence = response;
|
|
744
|
+
return sequence[callIndex % sequence.length](request);
|
|
745
|
+
}
|
|
746
|
+
if (typeof response === "function") {
|
|
747
|
+
return response(request);
|
|
748
|
+
}
|
|
749
|
+
return response;
|
|
750
|
+
}
|
|
751
|
+
function recordingAdapter(inner, sink) {
|
|
752
|
+
return {
|
|
753
|
+
createSource: (request) => {
|
|
754
|
+
const innerSource = inner.createSource(request);
|
|
755
|
+
const captured = [];
|
|
756
|
+
const recordedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
757
|
+
return {
|
|
758
|
+
stream: async function* () {
|
|
759
|
+
try {
|
|
760
|
+
for await (const chunk of innerSource.stream()) {
|
|
761
|
+
captured.push(chunk);
|
|
762
|
+
yield chunk;
|
|
763
|
+
}
|
|
764
|
+
} finally {
|
|
765
|
+
await sink.push({ recordedAt, request, chunks: captured });
|
|
766
|
+
}
|
|
767
|
+
},
|
|
768
|
+
abort: () => innerSource.abort()
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
function inMemorySink() {
|
|
774
|
+
const fixture = [];
|
|
775
|
+
return {
|
|
776
|
+
fixture,
|
|
777
|
+
push(turn) {
|
|
778
|
+
fixture.push(turn);
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
function replayAdapter(fixture) {
|
|
783
|
+
if (fixture.length === 0) {
|
|
784
|
+
throw new Error("replayAdapter: fixture is empty");
|
|
785
|
+
}
|
|
786
|
+
return mockAdapter({
|
|
787
|
+
response: fixture.map((turn) => turn.chunks)
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
|
|
541
791
|
// src/embedders/openai.ts
|
|
542
792
|
async function fetchAvailableModels(baseUrl, apiKey) {
|
|
543
793
|
const response = await fetch(`${baseUrl}/v1/models`, {
|
|
@@ -733,23 +983,30 @@ var grokEmbedder = createOpenAICompatibleEmbedder("Grok", "https://api.x.ai");
|
|
|
733
983
|
var kimiEmbedder = createOpenAICompatibleEmbedder("Kimi", "https://api.moonshot.ai");
|
|
734
984
|
|
|
735
985
|
exports.anthropic = anthropic;
|
|
986
|
+
exports.chunkText = chunkText;
|
|
736
987
|
exports.createAdapter = createAdapter;
|
|
737
988
|
exports.createOpenAICompatibleEmbedder = createOpenAICompatibleEmbedder;
|
|
738
989
|
exports.deepseek = deepseek;
|
|
739
990
|
exports.deepseekEmbedder = deepseekEmbedder;
|
|
991
|
+
exports.fetchWithRetry = fetchWithRetry;
|
|
740
992
|
exports.gemini = gemini;
|
|
741
993
|
exports.geminiEmbedder = geminiEmbedder;
|
|
742
994
|
exports.generic = generic;
|
|
743
995
|
exports.grok = grok;
|
|
744
996
|
exports.grokEmbedder = grokEmbedder;
|
|
997
|
+
exports.inMemorySink = inMemorySink;
|
|
745
998
|
exports.kimi = kimi;
|
|
746
999
|
exports.kimiEmbedder = kimiEmbedder;
|
|
747
1000
|
exports.langchain = langchain;
|
|
748
1001
|
exports.langgraph = langgraph;
|
|
1002
|
+
exports.mockAdapter = mockAdapter;
|
|
749
1003
|
exports.ollama = ollama;
|
|
750
1004
|
exports.ollamaEmbedder = ollamaEmbedder;
|
|
751
1005
|
exports.openai = openai;
|
|
752
1006
|
exports.openaiEmbedder = openaiEmbedder;
|
|
1007
|
+
exports.recordingAdapter = recordingAdapter;
|
|
1008
|
+
exports.replayAdapter = replayAdapter;
|
|
1009
|
+
exports.simulateStream = simulateStream;
|
|
753
1010
|
exports.vercelAI = vercelAI;
|
|
754
1011
|
//# sourceMappingURL=index.cjs.map
|
|
755
1012
|
//# sourceMappingURL=index.cjs.map
|