@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/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _agentskit_core from '@agentskit/core';
|
|
2
|
-
import { AdapterRequest, StreamChunk, AdapterFactory, EmbedFn } from '@agentskit/core';
|
|
2
|
+
import { AdapterRequest, StreamChunk, AdapterFactory, StreamSource, EmbedFn } from '@agentskit/core';
|
|
3
3
|
|
|
4
4
|
interface CreateAdapterConfig {
|
|
5
5
|
send: (request: AdapterRequest) => Promise<ReadableStream | Response>;
|
|
@@ -14,11 +14,75 @@ declare function createAdapter(config: CreateAdapterConfig): AdapterFactory;
|
|
|
14
14
|
|
|
15
15
|
declare function generic(config: GenericAdapterConfig): AdapterFactory;
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Retry knobs for adapter fetches. Tunable per call to createStreamSource.
|
|
19
|
+
*
|
|
20
|
+
* Default behavior:
|
|
21
|
+
* - 3 attempts total (1 initial + 2 retries)
|
|
22
|
+
* - exponential backoff: 500ms, 1000ms, 2000ms ... (capped at maxDelayMs)
|
|
23
|
+
* - full jitter on each delay
|
|
24
|
+
* - retry on HTTP 408, 429, 500, 502, 503, 504
|
|
25
|
+
* - retry on network errors (fetch throws)
|
|
26
|
+
* - DO NOT retry on 4xx other than 408/429 (those are bad requests / auth)
|
|
27
|
+
* - retries only the initial fetch — never mid-stream
|
|
28
|
+
* - respects Retry-After header when present
|
|
29
|
+
*/
|
|
30
|
+
interface RetryOptions {
|
|
31
|
+
maxAttempts?: number;
|
|
32
|
+
baseDelayMs?: number;
|
|
33
|
+
maxDelayMs?: number;
|
|
34
|
+
jitter?: boolean;
|
|
35
|
+
retryOn?: (info: {
|
|
36
|
+
error?: unknown;
|
|
37
|
+
response?: Response;
|
|
38
|
+
attempt: number;
|
|
39
|
+
}) => boolean;
|
|
40
|
+
/** Hook for tests + logging. Called after every failed attempt. */
|
|
41
|
+
onRetry?: (info: {
|
|
42
|
+
attempt: number;
|
|
43
|
+
delayMs: number;
|
|
44
|
+
reason: string;
|
|
45
|
+
}) => void;
|
|
46
|
+
/** Sleep override for tests. Defaults to setTimeout. */
|
|
47
|
+
sleep?: (ms: number) => Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Run a fetch with retries on transient failures. Returns the final
|
|
51
|
+
* Response (whether successful or not — caller decides), or throws if
|
|
52
|
+
* the AbortSignal fires or all attempts fail with a thrown error.
|
|
53
|
+
*/
|
|
54
|
+
declare function fetchWithRetry(doFetch: (signal: AbortSignal) => Promise<Response>, signal: AbortSignal, retryOpt?: RetryOptions): Promise<Response>;
|
|
55
|
+
/**
|
|
56
|
+
* Chunk-splitter that turns one large string into N streamable text chunks.
|
|
57
|
+
* Useful when a provider returns the full response in one shot and you
|
|
58
|
+
* want to feed it to a UI that expects streaming.
|
|
59
|
+
*
|
|
60
|
+
* Default splits by whitespace boundaries with a target chunk size of ~32
|
|
61
|
+
* characters.
|
|
62
|
+
*/
|
|
63
|
+
declare function chunkText(text: string, targetSize?: number): string[];
|
|
64
|
+
/**
|
|
65
|
+
* Build a StreamSource from a non-streaming fetch. The adapter is
|
|
66
|
+
* auto-completing: it fetches once, then yields the text as a sequence
|
|
67
|
+
* of chunks so UIs see the same streaming shape they'd see from a
|
|
68
|
+
* native streaming provider.
|
|
69
|
+
*
|
|
70
|
+
* Use this when you're wiring a provider that only has a non-streaming
|
|
71
|
+
* endpoint but you want consumers (useChat, the runtime) to get
|
|
72
|
+
* identical ergonomics.
|
|
73
|
+
*/
|
|
74
|
+
declare function simulateStream(doFetch: (signal: AbortSignal) => Promise<Response>, extractText: (response: Response) => Promise<string>, errorLabel: string, options?: {
|
|
75
|
+
chunkSize?: number;
|
|
76
|
+
delayMs?: number;
|
|
77
|
+
retry?: RetryOptions;
|
|
78
|
+
}): StreamSource;
|
|
79
|
+
|
|
17
80
|
interface AnthropicConfig {
|
|
18
81
|
apiKey: string;
|
|
19
82
|
model: string;
|
|
20
83
|
baseUrl?: string;
|
|
21
84
|
maxTokens?: number;
|
|
85
|
+
retry?: RetryOptions;
|
|
22
86
|
}
|
|
23
87
|
declare function anthropic(config: AnthropicConfig): AdapterFactory;
|
|
24
88
|
|
|
@@ -26,6 +90,7 @@ interface OpenAIConfig {
|
|
|
26
90
|
apiKey: string;
|
|
27
91
|
model: string;
|
|
28
92
|
baseUrl?: string;
|
|
93
|
+
retry?: RetryOptions;
|
|
29
94
|
}
|
|
30
95
|
declare function openai(config: OpenAIConfig): AdapterFactory;
|
|
31
96
|
|
|
@@ -33,6 +98,7 @@ interface GeminiConfig {
|
|
|
33
98
|
apiKey: string;
|
|
34
99
|
model: string;
|
|
35
100
|
baseUrl?: string;
|
|
101
|
+
retry?: RetryOptions;
|
|
36
102
|
}
|
|
37
103
|
declare function gemini(config: GeminiConfig): AdapterFactory;
|
|
38
104
|
|
|
@@ -46,6 +112,7 @@ declare const grok: (config: OpenAICompatibleConfig) => _agentskit_core.AdapterF
|
|
|
46
112
|
interface OllamaConfig {
|
|
47
113
|
model: string;
|
|
48
114
|
baseUrl?: string;
|
|
115
|
+
retry?: RetryOptions;
|
|
49
116
|
}
|
|
50
117
|
declare function ollama(config: OllamaConfig): AdapterFactory;
|
|
51
118
|
|
|
@@ -74,9 +141,90 @@ declare function langgraph(config: LangGraphConfig): AdapterFactory;
|
|
|
74
141
|
interface VercelAIConfig {
|
|
75
142
|
api: string;
|
|
76
143
|
headers?: Record<string, string>;
|
|
144
|
+
retry?: RetryOptions;
|
|
77
145
|
}
|
|
78
146
|
declare function vercelAI(config: VercelAIConfig): AdapterFactory;
|
|
79
147
|
|
|
148
|
+
type MockResponse = StreamChunk[] | ((request: AdapterRequest) => StreamChunk[]);
|
|
149
|
+
interface MockAdapterOptions {
|
|
150
|
+
/**
|
|
151
|
+
* Static chunks, a request-aware function, or a sequence of responses
|
|
152
|
+
* (the i-th call returns the i-th item, looping when exhausted).
|
|
153
|
+
*/
|
|
154
|
+
response: MockResponse | MockResponse[];
|
|
155
|
+
/** ms between yielded chunks. Default 0 (synchronous). */
|
|
156
|
+
delayMs?: number;
|
|
157
|
+
/** Track every request the adapter received. Useful for assertions. */
|
|
158
|
+
history?: AdapterRequest[];
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* A deterministic adapter for tests, demos, and dry-run experiments.
|
|
162
|
+
*
|
|
163
|
+
* Conforms to ADR 0001 — Adapter contract:
|
|
164
|
+
* - createSource is pure (A1) — no work until stream() runs
|
|
165
|
+
* - Always emits a terminal chunk (A3)
|
|
166
|
+
* - abort() is safe (A6)
|
|
167
|
+
* - Does not mutate input messages (A7)
|
|
168
|
+
*
|
|
169
|
+
* Examples:
|
|
170
|
+
*
|
|
171
|
+
* // Static
|
|
172
|
+
* const adapter = mockAdapter({
|
|
173
|
+
* response: [
|
|
174
|
+
* { type: 'text', content: 'Hello!' },
|
|
175
|
+
* { type: 'done' },
|
|
176
|
+
* ],
|
|
177
|
+
* })
|
|
178
|
+
*
|
|
179
|
+
* // Request-aware
|
|
180
|
+
* const adapter = mockAdapter({
|
|
181
|
+
* response: req => {
|
|
182
|
+
* const last = req.messages[req.messages.length - 1]?.content ?? ''
|
|
183
|
+
* return [
|
|
184
|
+
* { type: 'text', content: 'Echo: ' + last },
|
|
185
|
+
* { type: 'done' },
|
|
186
|
+
* ]
|
|
187
|
+
* },
|
|
188
|
+
* })
|
|
189
|
+
*
|
|
190
|
+
* // Sequenced — different output each call
|
|
191
|
+
* const adapter = mockAdapter({
|
|
192
|
+
* response: [
|
|
193
|
+
* [{ type: 'text', content: 'first' }, { type: 'done' }],
|
|
194
|
+
* [{ type: 'text', content: 'second' }, { type: 'done' }],
|
|
195
|
+
* ],
|
|
196
|
+
* })
|
|
197
|
+
*/
|
|
198
|
+
declare function mockAdapter(options: MockAdapterOptions): AdapterFactory;
|
|
199
|
+
interface RecordedTurn {
|
|
200
|
+
/** ISO timestamp when this turn was recorded. */
|
|
201
|
+
recordedAt: string;
|
|
202
|
+
/** The request that produced this turn. */
|
|
203
|
+
request: AdapterRequest;
|
|
204
|
+
/** Every chunk yielded by the wrapped adapter. */
|
|
205
|
+
chunks: StreamChunk[];
|
|
206
|
+
}
|
|
207
|
+
type RecordingFixture = RecordedTurn[];
|
|
208
|
+
interface RecordingSink {
|
|
209
|
+
push(turn: RecordedTurn): void | Promise<void>;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Wrap a real adapter so every turn is captured to a sink. Use this in
|
|
213
|
+
* dev to build up a fixture, then replay with replayAdapter() in tests.
|
|
214
|
+
*/
|
|
215
|
+
declare function recordingAdapter(inner: AdapterFactory, sink: RecordingSink): AdapterFactory;
|
|
216
|
+
/**
|
|
217
|
+
* In-memory recording sink — useful for tests and ephemeral capture.
|
|
218
|
+
*/
|
|
219
|
+
declare function inMemorySink(): RecordingSink & {
|
|
220
|
+
fixture: RecordingFixture;
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* Replay an adapter from a recorded fixture. Each turn maps 1:1 to a
|
|
224
|
+
* recorded entry by index — call N replays fixture[N % fixture.length].
|
|
225
|
+
*/
|
|
226
|
+
declare function replayAdapter(fixture: RecordingFixture): AdapterFactory;
|
|
227
|
+
|
|
80
228
|
interface OpenAIEmbedderConfig {
|
|
81
229
|
apiKey: string;
|
|
82
230
|
model?: string;
|
|
@@ -116,4 +264,4 @@ interface KimiEmbedderConfig extends OpenAICompatibleEmbedderConfig {
|
|
|
116
264
|
}
|
|
117
265
|
declare const kimiEmbedder: (config: OpenAICompatibleEmbedderConfig) => _agentskit_core.EmbedFn;
|
|
118
266
|
|
|
119
|
-
export { type AnthropicConfig, type CreateAdapterConfig, type DeepSeekConfig, type DeepSeekEmbedderConfig, type GeminiConfig, type GeminiEmbedderConfig, type GenericAdapterConfig, type GrokConfig, type GrokEmbedderConfig, type KimiConfig, type KimiEmbedderConfig, type LangChainConfig, type LangGraphConfig, type OllamaConfig, type OllamaEmbedderConfig, type OpenAICompatibleEmbedderConfig, type OpenAIConfig, type OpenAIEmbedderConfig, type VercelAIConfig, anthropic, createAdapter, createOpenAICompatibleEmbedder, deepseek, deepseekEmbedder, gemini, geminiEmbedder, generic, grok, grokEmbedder, kimi, kimiEmbedder, langchain, langgraph, ollama, ollamaEmbedder, openai, openaiEmbedder, vercelAI };
|
|
267
|
+
export { type AnthropicConfig, type CreateAdapterConfig, type DeepSeekConfig, type DeepSeekEmbedderConfig, type GeminiConfig, type GeminiEmbedderConfig, type GenericAdapterConfig, type GrokConfig, type GrokEmbedderConfig, type KimiConfig, type KimiEmbedderConfig, type LangChainConfig, type LangGraphConfig, type MockAdapterOptions, type MockResponse, type OllamaConfig, type OllamaEmbedderConfig, type OpenAICompatibleEmbedderConfig, type OpenAIConfig, type OpenAIEmbedderConfig, type RecordedTurn, type RecordingFixture, type RecordingSink, type RetryOptions, type VercelAIConfig, anthropic, chunkText, createAdapter, createOpenAICompatibleEmbedder, deepseek, deepseekEmbedder, fetchWithRetry, gemini, geminiEmbedder, generic, grok, grokEmbedder, inMemorySink, kimi, kimiEmbedder, langchain, langgraph, mockAdapter, ollama, ollamaEmbedder, openai, openaiEmbedder, recordingAdapter, replayAdapter, simulateStream, vercelAI };
|
package/dist/index.js
CHANGED
|
@@ -253,12 +253,73 @@ async function* parseOllamaStream(stream) {
|
|
|
253
253
|
}
|
|
254
254
|
yield { type: "done" };
|
|
255
255
|
}
|
|
256
|
-
|
|
256
|
+
var DEFAULT_RETRY = {
|
|
257
|
+
maxAttempts: 3,
|
|
258
|
+
baseDelayMs: 500,
|
|
259
|
+
maxDelayMs: 8e3,
|
|
260
|
+
jitter: true,
|
|
261
|
+
retryOn: ({ error, response }) => {
|
|
262
|
+
if (response) {
|
|
263
|
+
return [408, 429, 500, 502, 503, 504].includes(response.status);
|
|
264
|
+
}
|
|
265
|
+
if (error instanceof DOMException && error.name === "AbortError") return false;
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
function defaultSleep(ms) {
|
|
270
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
271
|
+
}
|
|
272
|
+
function computeDelay(attempt, opts) {
|
|
273
|
+
const exp = Math.min(opts.maxDelayMs, opts.baseDelayMs * Math.pow(2, attempt - 1));
|
|
274
|
+
if (!opts.jitter) return exp;
|
|
275
|
+
return Math.floor(Math.random() * exp);
|
|
276
|
+
}
|
|
277
|
+
function parseRetryAfter(value) {
|
|
278
|
+
if (!value) return void 0;
|
|
279
|
+
const n = Number(value);
|
|
280
|
+
if (!Number.isNaN(n)) return n * 1e3;
|
|
281
|
+
const date = Date.parse(value);
|
|
282
|
+
if (!Number.isNaN(date)) return Math.max(0, date - Date.now());
|
|
283
|
+
return void 0;
|
|
284
|
+
}
|
|
285
|
+
async function fetchWithRetry(doFetch, signal, retryOpt = {}) {
|
|
286
|
+
const opts = {
|
|
287
|
+
...DEFAULT_RETRY,
|
|
288
|
+
...retryOpt
|
|
289
|
+
};
|
|
290
|
+
const sleep = retryOpt.sleep ?? defaultSleep;
|
|
291
|
+
let lastError;
|
|
292
|
+
for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
|
|
293
|
+
if (signal.aborted) throw new DOMException("Aborted", "AbortError");
|
|
294
|
+
try {
|
|
295
|
+
const response = await doFetch(signal);
|
|
296
|
+
if (response.ok) return response;
|
|
297
|
+
if (attempt >= opts.maxAttempts || !opts.retryOn({ response, attempt })) {
|
|
298
|
+
return response;
|
|
299
|
+
}
|
|
300
|
+
const retryAfterMs = parseRetryAfter(response.headers.get("retry-after"));
|
|
301
|
+
const delay = retryAfterMs ?? computeDelay(attempt, opts);
|
|
302
|
+
retryOpt.onRetry?.({ attempt, delayMs: delay, reason: `HTTP ${response.status}` });
|
|
303
|
+
await sleep(delay);
|
|
304
|
+
response.body?.cancel().catch(() => {
|
|
305
|
+
});
|
|
306
|
+
} catch (err) {
|
|
307
|
+
lastError = err;
|
|
308
|
+
if (err instanceof DOMException && err.name === "AbortError") throw err;
|
|
309
|
+
if (attempt >= opts.maxAttempts || !opts.retryOn({ error: err, attempt })) throw err;
|
|
310
|
+
const delay = computeDelay(attempt, opts);
|
|
311
|
+
retryOpt.onRetry?.({ attempt, delayMs: delay, reason: err.message });
|
|
312
|
+
await sleep(delay);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
throw lastError ?? new Error("fetchWithRetry: exhausted attempts");
|
|
316
|
+
}
|
|
317
|
+
function createStreamSource(doFetch, parse, errorLabel, retry) {
|
|
257
318
|
let abortController = new AbortController();
|
|
258
319
|
return {
|
|
259
320
|
stream: async function* () {
|
|
260
321
|
try {
|
|
261
|
-
const response = await doFetch
|
|
322
|
+
const response = await fetchWithRetry(doFetch, abortController.signal, retry ?? {});
|
|
262
323
|
if (!response.ok || !response.body) {
|
|
263
324
|
yield { type: "error", content: `${errorLabel} error: ${response.status}` };
|
|
264
325
|
return;
|
|
@@ -278,11 +339,78 @@ function createStreamSource(doFetch, parse, errorLabel) {
|
|
|
278
339
|
}
|
|
279
340
|
};
|
|
280
341
|
}
|
|
342
|
+
function chunkText(text, targetSize = 32) {
|
|
343
|
+
if (text.length <= targetSize) return [text];
|
|
344
|
+
const out = [];
|
|
345
|
+
let i = 0;
|
|
346
|
+
while (i < text.length) {
|
|
347
|
+
let end = Math.min(text.length, i + targetSize);
|
|
348
|
+
if (end < text.length) {
|
|
349
|
+
const nextSpace = text.indexOf(" ", end);
|
|
350
|
+
if (nextSpace !== -1 && nextSpace - end <= 8) end = nextSpace + 1;
|
|
351
|
+
}
|
|
352
|
+
out.push(text.slice(i, end));
|
|
353
|
+
i = end;
|
|
354
|
+
}
|
|
355
|
+
return out;
|
|
356
|
+
}
|
|
357
|
+
function simulateStream(doFetch, extractText, errorLabel, options = {}) {
|
|
358
|
+
const { chunkSize = 32, delayMs = 8, retry } = options;
|
|
359
|
+
let abortController = new AbortController();
|
|
360
|
+
return {
|
|
361
|
+
stream: async function* () {
|
|
362
|
+
try {
|
|
363
|
+
const response = await fetchWithRetry(doFetch, abortController.signal, retry ?? {});
|
|
364
|
+
if (!response.ok) {
|
|
365
|
+
yield { type: "error", content: `${errorLabel} error: ${response.status}` };
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const text = await extractText(response);
|
|
369
|
+
const chunks = chunkText(text, chunkSize);
|
|
370
|
+
for (const chunk of chunks) {
|
|
371
|
+
if (abortController === null) return;
|
|
372
|
+
if (delayMs > 0) {
|
|
373
|
+
await new Promise((resolve2, reject) => {
|
|
374
|
+
const timer = setTimeout(resolve2, delayMs);
|
|
375
|
+
abortController?.signal.addEventListener(
|
|
376
|
+
"abort",
|
|
377
|
+
() => {
|
|
378
|
+
clearTimeout(timer);
|
|
379
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
380
|
+
},
|
|
381
|
+
{ once: true }
|
|
382
|
+
);
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
yield { type: "text", content: chunk };
|
|
386
|
+
}
|
|
387
|
+
yield { type: "done" };
|
|
388
|
+
} catch (err) {
|
|
389
|
+
if (err instanceof DOMException && err.name === "AbortError") return;
|
|
390
|
+
yield {
|
|
391
|
+
type: "error",
|
|
392
|
+
content: err instanceof Error ? err.message : String(err)
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
abort: () => {
|
|
397
|
+
abortController?.abort();
|
|
398
|
+
abortController = null;
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
}
|
|
281
402
|
|
|
282
403
|
// src/anthropic.ts
|
|
283
404
|
function anthropic(config) {
|
|
284
|
-
const { apiKey, model, baseUrl = "https://api.anthropic.com", maxTokens = 4096 } = config;
|
|
405
|
+
const { apiKey, model, baseUrl = "https://api.anthropic.com", maxTokens = 4096, retry } = config;
|
|
285
406
|
return {
|
|
407
|
+
capabilities: {
|
|
408
|
+
streaming: true,
|
|
409
|
+
tools: true,
|
|
410
|
+
reasoning: model.includes("sonnet") || model.includes("opus"),
|
|
411
|
+
multiModal: true,
|
|
412
|
+
usage: true
|
|
413
|
+
},
|
|
286
414
|
createSource: (request) => {
|
|
287
415
|
const body = {
|
|
288
416
|
model,
|
|
@@ -308,7 +436,8 @@ function anthropic(config) {
|
|
|
308
436
|
signal
|
|
309
437
|
}),
|
|
310
438
|
parseAnthropicStream,
|
|
311
|
-
"Anthropic API"
|
|
439
|
+
"Anthropic API",
|
|
440
|
+
retry
|
|
312
441
|
);
|
|
313
442
|
}
|
|
314
443
|
};
|
|
@@ -316,8 +445,17 @@ function anthropic(config) {
|
|
|
316
445
|
|
|
317
446
|
// src/openai.ts
|
|
318
447
|
function openai(config) {
|
|
319
|
-
const { apiKey, model, baseUrl = "https://api.openai.com" } = config;
|
|
448
|
+
const { apiKey, model, baseUrl = "https://api.openai.com", retry } = config;
|
|
320
449
|
return {
|
|
450
|
+
capabilities: {
|
|
451
|
+
streaming: true,
|
|
452
|
+
tools: true,
|
|
453
|
+
// o1 / o3 models emit reasoning; older models don't. Accurate per-model
|
|
454
|
+
// detection would need a model registry; 'true' is the safer default here.
|
|
455
|
+
reasoning: model.startsWith("o1") || model.startsWith("o3"),
|
|
456
|
+
multiModal: model.startsWith("gpt-4") || model.startsWith("o"),
|
|
457
|
+
usage: true
|
|
458
|
+
},
|
|
321
459
|
createSource: (request) => {
|
|
322
460
|
const body = {
|
|
323
461
|
model,
|
|
@@ -345,7 +483,8 @@ function openai(config) {
|
|
|
345
483
|
signal
|
|
346
484
|
}),
|
|
347
485
|
parseOpenAIStream,
|
|
348
|
-
"OpenAI API"
|
|
486
|
+
"OpenAI API",
|
|
487
|
+
retry
|
|
349
488
|
);
|
|
350
489
|
}
|
|
351
490
|
};
|
|
@@ -353,8 +492,14 @@ function openai(config) {
|
|
|
353
492
|
|
|
354
493
|
// src/gemini.ts
|
|
355
494
|
function gemini(config) {
|
|
356
|
-
const { apiKey, model, baseUrl = "https://generativelanguage.googleapis.com" } = config;
|
|
495
|
+
const { apiKey, model, baseUrl = "https://generativelanguage.googleapis.com", retry } = config;
|
|
357
496
|
return {
|
|
497
|
+
capabilities: {
|
|
498
|
+
streaming: true,
|
|
499
|
+
tools: true,
|
|
500
|
+
multiModal: true,
|
|
501
|
+
usage: true
|
|
502
|
+
},
|
|
358
503
|
createSource: (request) => {
|
|
359
504
|
const body = {
|
|
360
505
|
contents: request.messages.filter((message) => message.role !== "system").map((message) => ({
|
|
@@ -377,7 +522,8 @@ function gemini(config) {
|
|
|
377
522
|
}
|
|
378
523
|
),
|
|
379
524
|
parseGeminiStream,
|
|
380
|
-
"Gemini API"
|
|
525
|
+
"Gemini API",
|
|
526
|
+
retry
|
|
381
527
|
);
|
|
382
528
|
}
|
|
383
529
|
};
|
|
@@ -398,8 +544,14 @@ var grok = createOpenAICompatibleAdapter("https://api.x.ai");
|
|
|
398
544
|
|
|
399
545
|
// src/ollama.ts
|
|
400
546
|
function ollama(config) {
|
|
401
|
-
const { model, baseUrl = "http://localhost:11434" } = config;
|
|
547
|
+
const { model, baseUrl = "http://localhost:11434", retry } = config;
|
|
402
548
|
return {
|
|
549
|
+
capabilities: {
|
|
550
|
+
streaming: true,
|
|
551
|
+
tools: false,
|
|
552
|
+
// varies by model; default 'false' — enable via capabilities.extensions if your model supports it
|
|
553
|
+
multiModal: model.includes("llava") || model.includes("vision")
|
|
554
|
+
},
|
|
403
555
|
createSource: (request) => {
|
|
404
556
|
const body = {
|
|
405
557
|
model,
|
|
@@ -417,7 +569,8 @@ function ollama(config) {
|
|
|
417
569
|
signal
|
|
418
570
|
}),
|
|
419
571
|
parseOllamaStream,
|
|
420
|
-
"Ollama API"
|
|
572
|
+
"Ollama API",
|
|
573
|
+
retry
|
|
421
574
|
);
|
|
422
575
|
}
|
|
423
576
|
};
|
|
@@ -514,8 +667,12 @@ async function* parseVercelStream(stream) {
|
|
|
514
667
|
yield { type: "done" };
|
|
515
668
|
}
|
|
516
669
|
function vercelAI(config) {
|
|
517
|
-
const { api, headers = {} } = config;
|
|
670
|
+
const { api, headers = {}, retry } = config;
|
|
518
671
|
return {
|
|
672
|
+
capabilities: {
|
|
673
|
+
// Vercel AI routes can do any of these — whether they do depends on
|
|
674
|
+
// your route handler. Omit the field instead of lying.
|
|
675
|
+
},
|
|
519
676
|
createSource: (request) => {
|
|
520
677
|
const body = {
|
|
521
678
|
messages: request.messages.map((message) => ({ role: message.role, content: message.content })),
|
|
@@ -530,12 +687,105 @@ function vercelAI(config) {
|
|
|
530
687
|
signal
|
|
531
688
|
}),
|
|
532
689
|
parseVercelStream,
|
|
533
|
-
"API"
|
|
690
|
+
"API",
|
|
691
|
+
retry
|
|
534
692
|
);
|
|
535
693
|
}
|
|
536
694
|
};
|
|
537
695
|
}
|
|
538
696
|
|
|
697
|
+
// src/mock.ts
|
|
698
|
+
function mockAdapter(options) {
|
|
699
|
+
const { response, delayMs = 0, history } = options;
|
|
700
|
+
let callIndex = 0;
|
|
701
|
+
return {
|
|
702
|
+
capabilities: {
|
|
703
|
+
streaming: true,
|
|
704
|
+
tools: true,
|
|
705
|
+
reasoning: true,
|
|
706
|
+
multiModal: true,
|
|
707
|
+
usage: true
|
|
708
|
+
},
|
|
709
|
+
createSource: (request) => {
|
|
710
|
+
history?.push(request);
|
|
711
|
+
const myCall = callIndex++;
|
|
712
|
+
let cancelled = false;
|
|
713
|
+
return {
|
|
714
|
+
stream: async function* () {
|
|
715
|
+
const chunks = resolve(response, myCall, request);
|
|
716
|
+
let endedExplicitly = false;
|
|
717
|
+
for (const chunk of chunks) {
|
|
718
|
+
if (cancelled) return;
|
|
719
|
+
if (delayMs > 0) await new Promise((r) => setTimeout(r, delayMs));
|
|
720
|
+
yield chunk;
|
|
721
|
+
if (chunk.type === "done" || chunk.type === "error") {
|
|
722
|
+
endedExplicitly = true;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
if (!endedExplicitly) yield { type: "done" };
|
|
726
|
+
},
|
|
727
|
+
abort: () => {
|
|
728
|
+
cancelled = true;
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
function resolve(response, callIndex, request) {
|
|
735
|
+
if (Array.isArray(response) && response.length > 0 && Array.isArray(response[0])) {
|
|
736
|
+
const sequence = response;
|
|
737
|
+
const item = sequence[callIndex % sequence.length];
|
|
738
|
+
return item;
|
|
739
|
+
}
|
|
740
|
+
if (Array.isArray(response) && response.length > 0 && typeof response[0] === "function") {
|
|
741
|
+
const sequence = response;
|
|
742
|
+
return sequence[callIndex % sequence.length](request);
|
|
743
|
+
}
|
|
744
|
+
if (typeof response === "function") {
|
|
745
|
+
return response(request);
|
|
746
|
+
}
|
|
747
|
+
return response;
|
|
748
|
+
}
|
|
749
|
+
function recordingAdapter(inner, sink) {
|
|
750
|
+
return {
|
|
751
|
+
createSource: (request) => {
|
|
752
|
+
const innerSource = inner.createSource(request);
|
|
753
|
+
const captured = [];
|
|
754
|
+
const recordedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
755
|
+
return {
|
|
756
|
+
stream: async function* () {
|
|
757
|
+
try {
|
|
758
|
+
for await (const chunk of innerSource.stream()) {
|
|
759
|
+
captured.push(chunk);
|
|
760
|
+
yield chunk;
|
|
761
|
+
}
|
|
762
|
+
} finally {
|
|
763
|
+
await sink.push({ recordedAt, request, chunks: captured });
|
|
764
|
+
}
|
|
765
|
+
},
|
|
766
|
+
abort: () => innerSource.abort()
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
function inMemorySink() {
|
|
772
|
+
const fixture = [];
|
|
773
|
+
return {
|
|
774
|
+
fixture,
|
|
775
|
+
push(turn) {
|
|
776
|
+
fixture.push(turn);
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
function replayAdapter(fixture) {
|
|
781
|
+
if (fixture.length === 0) {
|
|
782
|
+
throw new Error("replayAdapter: fixture is empty");
|
|
783
|
+
}
|
|
784
|
+
return mockAdapter({
|
|
785
|
+
response: fixture.map((turn) => turn.chunks)
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
|
|
539
789
|
// src/embedders/openai.ts
|
|
540
790
|
async function fetchAvailableModels(baseUrl, apiKey) {
|
|
541
791
|
const response = await fetch(`${baseUrl}/v1/models`, {
|
|
@@ -730,6 +980,6 @@ var grokEmbedder = createOpenAICompatibleEmbedder("Grok", "https://api.x.ai");
|
|
|
730
980
|
// src/embedders/kimi.ts
|
|
731
981
|
var kimiEmbedder = createOpenAICompatibleEmbedder("Kimi", "https://api.moonshot.ai");
|
|
732
982
|
|
|
733
|
-
export { anthropic, createAdapter, createOpenAICompatibleEmbedder, deepseek, deepseekEmbedder, gemini, geminiEmbedder, generic, grok, grokEmbedder, kimi, kimiEmbedder, langchain, langgraph, ollama, ollamaEmbedder, openai, openaiEmbedder, vercelAI };
|
|
983
|
+
export { anthropic, chunkText, createAdapter, createOpenAICompatibleEmbedder, deepseek, deepseekEmbedder, fetchWithRetry, gemini, geminiEmbedder, generic, grok, grokEmbedder, inMemorySink, kimi, kimiEmbedder, langchain, langgraph, mockAdapter, ollama, ollamaEmbedder, openai, openaiEmbedder, recordingAdapter, replayAdapter, simulateStream, vercelAI };
|
|
734
984
|
//# sourceMappingURL=index.js.map
|
|
735
985
|
//# sourceMappingURL=index.js.map
|