@diabolicallabs/llm-client 0.2.0 → 0.3.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 +88 -3
- package/dist/index.d.ts +51 -5
- package/dist/index.js +302 -66
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,6 +7,13 @@ var LlmError = class extends Error {
|
|
|
7
7
|
provider;
|
|
8
8
|
statusCode;
|
|
9
9
|
retryable;
|
|
10
|
+
/**
|
|
11
|
+
* Optional error kind discriminator. Present on errors produced by the abort/timeout/stall
|
|
12
|
+
* machinery (v0.3.0+). May be undefined on errors from providers that pre-date the kind field
|
|
13
|
+
* or on errors that fall through to the generic normalization path.
|
|
14
|
+
* Typed as LlmErrorKind | undefined to satisfy exactOptionalPropertyTypes.
|
|
15
|
+
*/
|
|
16
|
+
kind;
|
|
10
17
|
// `cause` is declared on Error in lib.es2022.error.d.ts as `cause?: unknown`
|
|
11
18
|
// We override it here to make it always present (not optional) after construction.
|
|
12
19
|
cause;
|
|
@@ -15,10 +22,128 @@ var LlmError = class extends Error {
|
|
|
15
22
|
this.provider = opts.provider;
|
|
16
23
|
this.statusCode = opts.statusCode;
|
|
17
24
|
this.retryable = opts.retryable;
|
|
25
|
+
this.kind = opts.kind;
|
|
18
26
|
this.cause = opts.cause;
|
|
19
27
|
}
|
|
20
28
|
};
|
|
21
29
|
|
|
30
|
+
// src/abort.ts
|
|
31
|
+
function createAttemptController(callerSignal, timeoutMs) {
|
|
32
|
+
const internal = new AbortController();
|
|
33
|
+
let reason;
|
|
34
|
+
const timer = setTimeout(() => {
|
|
35
|
+
reason ??= "timeout";
|
|
36
|
+
internal.abort(new Error("llm-client: timeout"));
|
|
37
|
+
}, timeoutMs);
|
|
38
|
+
timer.unref?.();
|
|
39
|
+
const onCallerAbort = () => {
|
|
40
|
+
reason ??= "caller";
|
|
41
|
+
internal.abort(callerSignal.reason);
|
|
42
|
+
};
|
|
43
|
+
if (callerSignal !== void 0) {
|
|
44
|
+
if (callerSignal.aborted) {
|
|
45
|
+
onCallerAbort();
|
|
46
|
+
} else {
|
|
47
|
+
callerSignal.addEventListener("abort", onCallerAbort, { once: true });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
signal: internal.signal,
|
|
52
|
+
abortReason: () => reason,
|
|
53
|
+
abort: (r) => {
|
|
54
|
+
reason ??= r;
|
|
55
|
+
internal.abort();
|
|
56
|
+
},
|
|
57
|
+
dispose: () => {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function cancellableSleep(ms, signal) {
|
|
64
|
+
return new Promise((resolve) => {
|
|
65
|
+
if (signal?.aborted) {
|
|
66
|
+
resolve();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const timer = setTimeout(() => {
|
|
70
|
+
signal?.removeEventListener("abort", onAbort);
|
|
71
|
+
resolve();
|
|
72
|
+
}, ms);
|
|
73
|
+
timer.unref?.();
|
|
74
|
+
const onAbort = () => {
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
resolve();
|
|
77
|
+
};
|
|
78
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async function* withStallTimeout(source, stallMs, ctl, provider) {
|
|
82
|
+
const it = source[Symbol.asyncIterator]();
|
|
83
|
+
while (true) {
|
|
84
|
+
let stallTimer;
|
|
85
|
+
const stallPromise = new Promise((_, reject) => {
|
|
86
|
+
stallTimer = setTimeout(() => {
|
|
87
|
+
ctl.abort("stall");
|
|
88
|
+
reject(
|
|
89
|
+
new LlmError({
|
|
90
|
+
provider,
|
|
91
|
+
kind: "stream_stall",
|
|
92
|
+
retryable: true,
|
|
93
|
+
message: `llm-client: no chunk for ${stallMs}ms`
|
|
94
|
+
})
|
|
95
|
+
);
|
|
96
|
+
}, stallMs);
|
|
97
|
+
});
|
|
98
|
+
try {
|
|
99
|
+
const next = await Promise.race([it.next(), stallPromise]);
|
|
100
|
+
clearTimeout(stallTimer);
|
|
101
|
+
if (next.done) return;
|
|
102
|
+
yield next.value;
|
|
103
|
+
} catch (err) {
|
|
104
|
+
clearTimeout(stallTimer);
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function classifyAbort(err, abortReason, provider) {
|
|
110
|
+
const controllerFired = abortReason !== void 0;
|
|
111
|
+
if (!controllerFired && !isAbortError(err)) return err;
|
|
112
|
+
switch (abortReason) {
|
|
113
|
+
case "timeout":
|
|
114
|
+
return new LlmError({
|
|
115
|
+
message: "llm-client: request timed out",
|
|
116
|
+
provider,
|
|
117
|
+
kind: "timeout",
|
|
118
|
+
retryable: true,
|
|
119
|
+
cause: err
|
|
120
|
+
});
|
|
121
|
+
case "stall":
|
|
122
|
+
return new LlmError({
|
|
123
|
+
message: "llm-client: stream stalled",
|
|
124
|
+
provider,
|
|
125
|
+
kind: "stream_stall",
|
|
126
|
+
retryable: true,
|
|
127
|
+
cause: err
|
|
128
|
+
});
|
|
129
|
+
case "caller":
|
|
130
|
+
default:
|
|
131
|
+
return new LlmError({
|
|
132
|
+
message: "llm-client: cancelled by caller",
|
|
133
|
+
provider,
|
|
134
|
+
kind: "cancelled",
|
|
135
|
+
retryable: false,
|
|
136
|
+
cause: err
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function isAbortError(err) {
|
|
141
|
+
if (err instanceof Error && err.name === "AbortError") return true;
|
|
142
|
+
if (typeof DOMException !== "undefined" && err instanceof DOMException && err.name === "AbortError")
|
|
143
|
+
return true;
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
22
147
|
// src/retry.ts
|
|
23
148
|
var RETRYABLE_HTTP_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
24
149
|
var RETRYABLE_ERROR_CODES = /* @__PURE__ */ new Set(["ECONNRESET", "ETIMEDOUT", "ECONNABORTED"]);
|
|
@@ -35,19 +160,32 @@ function computeBackoffMs(attempt, baseDelayMs) {
|
|
|
35
160
|
const ceiling = baseDelayMs * 2 ** attempt;
|
|
36
161
|
return Math.random() * ceiling;
|
|
37
162
|
}
|
|
163
|
+
function mergeRetryOptsWithSignal(base, signal) {
|
|
164
|
+
return signal !== void 0 ? { ...base, signal } : { ...base };
|
|
165
|
+
}
|
|
38
166
|
async function withRetry(fn, opts) {
|
|
39
167
|
let lastError;
|
|
40
168
|
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
|
|
169
|
+
if (opts.signal?.aborted === true) {
|
|
170
|
+
throw new LlmError({
|
|
171
|
+
message: "llm-client: cancelled by caller",
|
|
172
|
+
provider: opts.provider,
|
|
173
|
+
kind: "cancelled",
|
|
174
|
+
retryable: false,
|
|
175
|
+
cause: opts.signal.reason
|
|
176
|
+
});
|
|
177
|
+
}
|
|
41
178
|
try {
|
|
42
179
|
return await fn(attempt);
|
|
43
180
|
} catch (err) {
|
|
44
181
|
const llmErr = normalizeThrownError(err, opts.provider);
|
|
182
|
+
if (llmErr.kind === "cancelled") throw llmErr;
|
|
45
183
|
if (!llmErr.retryable || attempt === opts.maxRetries) {
|
|
46
184
|
throw llmErr;
|
|
47
185
|
}
|
|
48
186
|
lastError = llmErr;
|
|
49
187
|
const delayMs = computeBackoffMs(attempt, opts.baseDelayMs);
|
|
50
|
-
await
|
|
188
|
+
await cancellableSleep(delayMs, opts.signal);
|
|
51
189
|
}
|
|
52
190
|
}
|
|
53
191
|
throw lastError ?? new LlmError({
|
|
@@ -59,6 +197,15 @@ async function withRetry(fn, opts) {
|
|
|
59
197
|
function normalizeThrownError(err, provider) {
|
|
60
198
|
if (err instanceof LlmError) return err;
|
|
61
199
|
if (err instanceof Error) {
|
|
200
|
+
if (err.name === "AbortError" || typeof DOMException !== "undefined" && err instanceof DOMException && err.name === "AbortError") {
|
|
201
|
+
return new LlmError({
|
|
202
|
+
message: err.message || "llm-client: cancelled by caller",
|
|
203
|
+
provider,
|
|
204
|
+
kind: "cancelled",
|
|
205
|
+
retryable: false,
|
|
206
|
+
cause: err
|
|
207
|
+
});
|
|
208
|
+
}
|
|
62
209
|
const errWithCode = err;
|
|
63
210
|
const statusCode = errWithCode.status ?? errWithCode.statusCode;
|
|
64
211
|
if (errWithCode.code !== void 0 && isRetryableErrorCode(errWithCode.code)) {
|
|
@@ -67,24 +214,28 @@ function normalizeThrownError(err, provider) {
|
|
|
67
214
|
message: err.message,
|
|
68
215
|
provider,
|
|
69
216
|
statusCode,
|
|
217
|
+
kind: "network",
|
|
70
218
|
retryable: true,
|
|
71
219
|
cause: err
|
|
72
220
|
});
|
|
73
221
|
}
|
|
74
|
-
return new LlmError({ message: err.message, provider, retryable: true, cause: err });
|
|
222
|
+
return new LlmError({ message: err.message, provider, kind: "network", retryable: true, cause: err });
|
|
75
223
|
}
|
|
76
224
|
if (statusCode !== void 0) {
|
|
225
|
+
const retryable = isRetryableStatus(statusCode);
|
|
77
226
|
return new LlmError({
|
|
78
227
|
message: err.message,
|
|
79
228
|
provider,
|
|
80
229
|
statusCode,
|
|
81
|
-
retryable:
|
|
230
|
+
kind: retryable ? "http" : "http",
|
|
231
|
+
retryable,
|
|
82
232
|
cause: err
|
|
83
233
|
});
|
|
84
234
|
}
|
|
85
235
|
return new LlmError({
|
|
86
236
|
message: err.message,
|
|
87
237
|
provider,
|
|
238
|
+
kind: "unknown",
|
|
88
239
|
retryable: false,
|
|
89
240
|
cause: err
|
|
90
241
|
});
|
|
@@ -92,13 +243,11 @@ function normalizeThrownError(err, provider) {
|
|
|
92
243
|
return new LlmError({
|
|
93
244
|
message: String(err),
|
|
94
245
|
provider,
|
|
246
|
+
kind: "unknown",
|
|
95
247
|
retryable: false,
|
|
96
248
|
cause: err
|
|
97
249
|
});
|
|
98
250
|
}
|
|
99
|
-
function sleep(ms) {
|
|
100
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
101
|
-
}
|
|
102
251
|
|
|
103
252
|
// src/providers/anthropic.ts
|
|
104
253
|
var PROVIDER = "anthropic";
|
|
@@ -166,8 +315,10 @@ function createAnthropicProvider(config) {
|
|
|
166
315
|
async function complete(messages, options) {
|
|
167
316
|
const model = options?.model ?? config.model;
|
|
168
317
|
const { system, messages: anthropicMessages } = buildAnthropicMessages(messages);
|
|
318
|
+
const effectiveTimeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 3e4;
|
|
169
319
|
const start = Date.now();
|
|
170
320
|
return withRetry(async () => {
|
|
321
|
+
const ctl = createAttemptController(options?.signal, effectiveTimeoutMs);
|
|
171
322
|
try {
|
|
172
323
|
const params = {
|
|
173
324
|
model,
|
|
@@ -179,7 +330,7 @@ function createAnthropicProvider(config) {
|
|
|
179
330
|
if (temperature !== void 0) {
|
|
180
331
|
params.temperature = temperature;
|
|
181
332
|
}
|
|
182
|
-
const response = await client.messages.create(params);
|
|
333
|
+
const response = await client.messages.create(params, { signal: ctl.signal });
|
|
183
334
|
const content = response.content.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
184
335
|
return {
|
|
185
336
|
content,
|
|
@@ -188,13 +339,17 @@ function createAnthropicProvider(config) {
|
|
|
188
339
|
latencyMs: Date.now() - start
|
|
189
340
|
};
|
|
190
341
|
} catch (err) {
|
|
191
|
-
throw normalizeAnthropicError(err);
|
|
342
|
+
throw normalizeAnthropicError(classifyAbort(err, ctl.abortReason(), PROVIDER));
|
|
343
|
+
} finally {
|
|
344
|
+
ctl.dispose();
|
|
192
345
|
}
|
|
193
|
-
}, retryOpts);
|
|
346
|
+
}, mergeRetryOptsWithSignal(retryOpts, options?.signal));
|
|
194
347
|
}
|
|
195
348
|
async function* stream(messages, options) {
|
|
196
349
|
const model = options?.model ?? config.model;
|
|
197
350
|
const { system, messages: anthropicMessages } = buildAnthropicMessages(messages);
|
|
351
|
+
const effectiveTimeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 3e4;
|
|
352
|
+
const stallMs = options?.streamStallTimeoutMs ?? config.streamStallTimeoutMs ?? 3e4;
|
|
198
353
|
const params = {
|
|
199
354
|
model,
|
|
200
355
|
messages: anthropicMessages,
|
|
@@ -205,15 +360,23 @@ function createAnthropicProvider(config) {
|
|
|
205
360
|
if (streamTemperature !== void 0) {
|
|
206
361
|
params.temperature = streamTemperature;
|
|
207
362
|
}
|
|
363
|
+
const ctl = createAttemptController(options?.signal, effectiveTimeoutMs);
|
|
208
364
|
let sdkStream;
|
|
209
365
|
try {
|
|
210
|
-
sdkStream = client.messages.stream(params);
|
|
366
|
+
sdkStream = client.messages.stream(params, { signal: ctl.signal });
|
|
211
367
|
} catch (err) {
|
|
212
|
-
|
|
368
|
+
ctl.dispose();
|
|
369
|
+
throw normalizeAnthropicError(classifyAbort(err, ctl.abortReason(), PROVIDER));
|
|
213
370
|
}
|
|
214
371
|
let finalUsage;
|
|
215
372
|
try {
|
|
216
|
-
|
|
373
|
+
const stallWrapped = withStallTimeout(
|
|
374
|
+
sdkStream,
|
|
375
|
+
stallMs,
|
|
376
|
+
ctl,
|
|
377
|
+
PROVIDER
|
|
378
|
+
);
|
|
379
|
+
for await (const event of stallWrapped) {
|
|
217
380
|
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
|
|
218
381
|
yield { token: event.delta.text };
|
|
219
382
|
} else if (event.type === "message_delta" && "usage" in event) {
|
|
@@ -222,7 +385,9 @@ function createAnthropicProvider(config) {
|
|
|
222
385
|
}
|
|
223
386
|
}
|
|
224
387
|
} catch (err) {
|
|
225
|
-
throw normalizeAnthropicError(err);
|
|
388
|
+
throw normalizeAnthropicError(classifyAbort(err, ctl.abortReason(), PROVIDER));
|
|
389
|
+
} finally {
|
|
390
|
+
ctl.dispose();
|
|
226
391
|
}
|
|
227
392
|
if (finalUsage !== void 0) {
|
|
228
393
|
yield { token: "", usage: finalUsage };
|
|
@@ -334,8 +499,10 @@ function createDeepSeekProvider(config) {
|
|
|
334
499
|
async function complete(messages, options) {
|
|
335
500
|
const model = options?.model ?? config.model;
|
|
336
501
|
const chatMessages = buildMessages(messages);
|
|
502
|
+
const effectiveTimeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 3e4;
|
|
337
503
|
const start = Date.now();
|
|
338
504
|
return withRetry(async () => {
|
|
505
|
+
const ctl = createAttemptController(options?.signal, effectiveTimeoutMs);
|
|
339
506
|
try {
|
|
340
507
|
const params = {
|
|
341
508
|
model,
|
|
@@ -346,7 +513,7 @@ function createDeepSeekProvider(config) {
|
|
|
346
513
|
if (maxTokens !== void 0) params.max_tokens = maxTokens;
|
|
347
514
|
const temperature = options?.temperature ?? config.temperature;
|
|
348
515
|
if (temperature !== void 0) params.temperature = temperature;
|
|
349
|
-
const response = await client.chat.completions.create(params);
|
|
516
|
+
const response = await client.chat.completions.create(params, { signal: ctl.signal });
|
|
350
517
|
const content = response.choices.map((c) => c.message.content ?? "").join("");
|
|
351
518
|
return {
|
|
352
519
|
content,
|
|
@@ -355,13 +522,17 @@ function createDeepSeekProvider(config) {
|
|
|
355
522
|
latencyMs: Date.now() - start
|
|
356
523
|
};
|
|
357
524
|
} catch (err) {
|
|
358
|
-
throw normalizeDeepSeekError(err);
|
|
525
|
+
throw normalizeDeepSeekError(classifyAbort(err, ctl.abortReason(), PROVIDER2));
|
|
526
|
+
} finally {
|
|
527
|
+
ctl.dispose();
|
|
359
528
|
}
|
|
360
|
-
}, retryOpts);
|
|
529
|
+
}, mergeRetryOptsWithSignal(retryOpts, options?.signal));
|
|
361
530
|
}
|
|
362
531
|
async function* stream(messages, options) {
|
|
363
532
|
const model = options?.model ?? config.model;
|
|
364
533
|
const chatMessages = buildMessages(messages);
|
|
534
|
+
const effectiveTimeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 3e4;
|
|
535
|
+
const stallMs = options?.streamStallTimeoutMs ?? config.streamStallTimeoutMs ?? 3e4;
|
|
365
536
|
const params = {
|
|
366
537
|
model,
|
|
367
538
|
messages: chatMessages,
|
|
@@ -372,15 +543,17 @@ function createDeepSeekProvider(config) {
|
|
|
372
543
|
if (maxTokens !== void 0) params.max_tokens = maxTokens;
|
|
373
544
|
const temperature = options?.temperature ?? config.temperature;
|
|
374
545
|
if (temperature !== void 0) params.temperature = temperature;
|
|
546
|
+
const ctl = createAttemptController(options?.signal, effectiveTimeoutMs);
|
|
375
547
|
let sdkStream;
|
|
376
548
|
try {
|
|
377
|
-
sdkStream = await client.chat.completions.create(params);
|
|
549
|
+
sdkStream = await client.chat.completions.create(params, { signal: ctl.signal });
|
|
378
550
|
} catch (err) {
|
|
379
|
-
|
|
551
|
+
ctl.dispose();
|
|
552
|
+
throw normalizeDeepSeekError(classifyAbort(err, ctl.abortReason(), PROVIDER2));
|
|
380
553
|
}
|
|
381
554
|
let finalUsage;
|
|
382
555
|
try {
|
|
383
|
-
for await (const chunk of sdkStream) {
|
|
556
|
+
for await (const chunk of withStallTimeout(sdkStream, stallMs, ctl, PROVIDER2)) {
|
|
384
557
|
const delta = chunk.choices[0]?.delta.content;
|
|
385
558
|
if (delta !== void 0 && delta !== null && delta.length > 0) {
|
|
386
559
|
yield { token: delta };
|
|
@@ -390,7 +563,9 @@ function createDeepSeekProvider(config) {
|
|
|
390
563
|
}
|
|
391
564
|
}
|
|
392
565
|
} catch (err) {
|
|
393
|
-
throw normalizeDeepSeekError(err);
|
|
566
|
+
throw normalizeDeepSeekError(classifyAbort(err, ctl.abortReason(), PROVIDER2));
|
|
567
|
+
} finally {
|
|
568
|
+
ctl.dispose();
|
|
394
569
|
}
|
|
395
570
|
if (finalUsage !== void 0) {
|
|
396
571
|
yield { token: "", usage: finalUsage };
|
|
@@ -404,8 +579,10 @@ function createDeepSeekProvider(config) {
|
|
|
404
579
|
const augmentedMessages = [jsonSystemInstruction, ...messages];
|
|
405
580
|
const model = options?.model ?? config.model;
|
|
406
581
|
const chatMessages = buildMessages(augmentedMessages);
|
|
582
|
+
const effectiveTimeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 3e4;
|
|
407
583
|
const start = Date.now();
|
|
408
584
|
const rawResponse = await withRetry(async () => {
|
|
585
|
+
const ctl = createAttemptController(options?.signal, effectiveTimeoutMs);
|
|
409
586
|
try {
|
|
410
587
|
const params = {
|
|
411
588
|
model,
|
|
@@ -416,11 +593,13 @@ function createDeepSeekProvider(config) {
|
|
|
416
593
|
if (maxTokens !== void 0) params.max_tokens = maxTokens;
|
|
417
594
|
const temperature = options?.temperature ?? config.temperature;
|
|
418
595
|
if (temperature !== void 0) params.temperature = temperature;
|
|
419
|
-
return await client.chat.completions.create(params);
|
|
596
|
+
return await client.chat.completions.create(params, { signal: ctl.signal });
|
|
420
597
|
} catch (err) {
|
|
421
|
-
throw normalizeDeepSeekError(err);
|
|
598
|
+
throw normalizeDeepSeekError(classifyAbort(err, ctl.abortReason(), PROVIDER2));
|
|
599
|
+
} finally {
|
|
600
|
+
ctl.dispose();
|
|
422
601
|
}
|
|
423
|
-
}, retryOpts);
|
|
602
|
+
}, mergeRetryOptsWithSignal(retryOpts, options?.signal));
|
|
424
603
|
const rawContent = rawResponse.choices[0]?.message.content ?? "";
|
|
425
604
|
let parsed;
|
|
426
605
|
try {
|
|
@@ -498,11 +677,26 @@ function normalizeGeminiError(err) {
|
|
|
498
677
|
}
|
|
499
678
|
return normalizeThrownError(err, PROVIDER3);
|
|
500
679
|
}
|
|
680
|
+
function makeAbortRacePromise(signal) {
|
|
681
|
+
return new Promise((_, reject) => {
|
|
682
|
+
const onAbort = () => {
|
|
683
|
+
const e = new Error("AbortError");
|
|
684
|
+
e.name = "AbortError";
|
|
685
|
+
reject(e);
|
|
686
|
+
};
|
|
687
|
+
if (signal.aborted) {
|
|
688
|
+
onAbort();
|
|
689
|
+
} else {
|
|
690
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
}
|
|
501
694
|
function createGeminiProvider(config) {
|
|
695
|
+
const configTimeoutMs = config.timeoutMs ?? 3e4;
|
|
502
696
|
const ai = new GoogleGenAI({
|
|
503
697
|
apiKey: config.apiKey,
|
|
504
698
|
httpOptions: {
|
|
505
|
-
timeout:
|
|
699
|
+
timeout: configTimeoutMs * 2
|
|
506
700
|
}
|
|
507
701
|
});
|
|
508
702
|
const retryOpts = {
|
|
@@ -513,8 +707,10 @@ function createGeminiProvider(config) {
|
|
|
513
707
|
async function complete(messages, options) {
|
|
514
708
|
const model = options?.model ?? config.model;
|
|
515
709
|
const { system, contents } = buildGeminiContents(messages);
|
|
710
|
+
const effectiveTimeoutMs = options?.timeoutMs ?? configTimeoutMs;
|
|
516
711
|
const start = Date.now();
|
|
517
712
|
return withRetry(async () => {
|
|
713
|
+
const ctl = createAttemptController(options?.signal, effectiveTimeoutMs);
|
|
518
714
|
try {
|
|
519
715
|
const geminiConfig = {};
|
|
520
716
|
if (system !== void 0) geminiConfig.systemInstruction = system;
|
|
@@ -522,11 +718,10 @@ function createGeminiProvider(config) {
|
|
|
522
718
|
if (maxTokens !== void 0) geminiConfig.maxOutputTokens = maxTokens;
|
|
523
719
|
const temperature = options?.temperature ?? config.temperature;
|
|
524
720
|
if (temperature !== void 0) geminiConfig.temperature = temperature;
|
|
525
|
-
const response = await
|
|
526
|
-
model,
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
});
|
|
721
|
+
const response = await Promise.race([
|
|
722
|
+
ai.models.generateContent({ model, contents, config: geminiConfig }),
|
|
723
|
+
makeAbortRacePromise(ctl.signal)
|
|
724
|
+
]);
|
|
530
725
|
return {
|
|
531
726
|
content: response.text ?? "",
|
|
532
727
|
model,
|
|
@@ -534,32 +729,37 @@ function createGeminiProvider(config) {
|
|
|
534
729
|
latencyMs: Date.now() - start
|
|
535
730
|
};
|
|
536
731
|
} catch (err) {
|
|
537
|
-
throw normalizeGeminiError(err);
|
|
732
|
+
throw normalizeGeminiError(classifyAbort(err, ctl.abortReason(), PROVIDER3));
|
|
733
|
+
} finally {
|
|
734
|
+
ctl.dispose();
|
|
538
735
|
}
|
|
539
|
-
}, retryOpts);
|
|
736
|
+
}, mergeRetryOptsWithSignal(retryOpts, options?.signal));
|
|
540
737
|
}
|
|
541
738
|
async function* stream(messages, options) {
|
|
542
739
|
const model = options?.model ?? config.model;
|
|
543
740
|
const { system, contents } = buildGeminiContents(messages);
|
|
741
|
+
const effectiveTimeoutMs = options?.timeoutMs ?? configTimeoutMs;
|
|
742
|
+
const stallMs = options?.streamStallTimeoutMs ?? config.streamStallTimeoutMs ?? 3e4;
|
|
544
743
|
const geminiConfig = {};
|
|
545
744
|
if (system !== void 0) geminiConfig.systemInstruction = system;
|
|
546
745
|
const maxTokens = options?.maxTokens ?? config.maxTokens;
|
|
547
746
|
if (maxTokens !== void 0) geminiConfig.maxOutputTokens = maxTokens;
|
|
548
747
|
const temperature = options?.temperature ?? config.temperature;
|
|
549
748
|
if (temperature !== void 0) geminiConfig.temperature = temperature;
|
|
749
|
+
const ctl = createAttemptController(options?.signal, effectiveTimeoutMs);
|
|
550
750
|
let sdkStream;
|
|
551
751
|
try {
|
|
552
|
-
sdkStream = await
|
|
553
|
-
model,
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
});
|
|
752
|
+
sdkStream = await Promise.race([
|
|
753
|
+
ai.models.generateContentStream({ model, contents, config: geminiConfig }),
|
|
754
|
+
makeAbortRacePromise(ctl.signal)
|
|
755
|
+
]);
|
|
557
756
|
} catch (err) {
|
|
558
|
-
|
|
757
|
+
ctl.dispose();
|
|
758
|
+
throw normalizeGeminiError(classifyAbort(err, ctl.abortReason(), PROVIDER3));
|
|
559
759
|
}
|
|
560
760
|
let finalUsage;
|
|
561
761
|
try {
|
|
562
|
-
for await (const chunk of sdkStream) {
|
|
762
|
+
for await (const chunk of withStallTimeout(sdkStream, stallMs, ctl, PROVIDER3)) {
|
|
563
763
|
const text = chunk.text;
|
|
564
764
|
if (text !== void 0 && text.length > 0) {
|
|
565
765
|
yield { token: text };
|
|
@@ -569,7 +769,9 @@ function createGeminiProvider(config) {
|
|
|
569
769
|
}
|
|
570
770
|
}
|
|
571
771
|
} catch (err) {
|
|
572
|
-
throw normalizeGeminiError(err);
|
|
772
|
+
throw normalizeGeminiError(classifyAbort(err, ctl.abortReason(), PROVIDER3));
|
|
773
|
+
} finally {
|
|
774
|
+
ctl.dispose();
|
|
573
775
|
}
|
|
574
776
|
if (finalUsage !== void 0) {
|
|
575
777
|
yield { token: "", usage: finalUsage };
|
|
@@ -585,8 +787,10 @@ function createGeminiProvider(config) {
|
|
|
585
787
|
];
|
|
586
788
|
const model = options?.model ?? config.model;
|
|
587
789
|
const { system, contents } = buildGeminiContents(augmentedMessages);
|
|
790
|
+
const effectiveTimeoutMs = options?.timeoutMs ?? configTimeoutMs;
|
|
588
791
|
const start = Date.now();
|
|
589
792
|
const rawResponse = await withRetry(async () => {
|
|
793
|
+
const ctl = createAttemptController(options?.signal, effectiveTimeoutMs);
|
|
590
794
|
try {
|
|
591
795
|
const geminiConfig = {
|
|
592
796
|
// Instruct Gemini to return JSON directly
|
|
@@ -597,15 +801,16 @@ function createGeminiProvider(config) {
|
|
|
597
801
|
if (maxTokens !== void 0) geminiConfig.maxOutputTokens = maxTokens;
|
|
598
802
|
const temperature = options?.temperature ?? config.temperature;
|
|
599
803
|
if (temperature !== void 0) geminiConfig.temperature = temperature;
|
|
600
|
-
return await
|
|
601
|
-
model,
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
});
|
|
804
|
+
return await Promise.race([
|
|
805
|
+
ai.models.generateContent({ model, contents, config: geminiConfig }),
|
|
806
|
+
makeAbortRacePromise(ctl.signal)
|
|
807
|
+
]);
|
|
605
808
|
} catch (err) {
|
|
606
|
-
throw normalizeGeminiError(err);
|
|
809
|
+
throw normalizeGeminiError(classifyAbort(err, ctl.abortReason(), PROVIDER3));
|
|
810
|
+
} finally {
|
|
811
|
+
ctl.dispose();
|
|
607
812
|
}
|
|
608
|
-
}, retryOpts);
|
|
813
|
+
}, mergeRetryOptsWithSignal(retryOpts, options?.signal));
|
|
609
814
|
const rawContent = rawResponse.text ?? "";
|
|
610
815
|
let parsed;
|
|
611
816
|
try {
|
|
@@ -703,8 +908,10 @@ function createOpenAIProvider(config) {
|
|
|
703
908
|
async function complete(messages, options) {
|
|
704
909
|
const model = options?.model ?? config.model;
|
|
705
910
|
const openAIMessages = buildOpenAIMessages(messages);
|
|
911
|
+
const effectiveTimeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 3e4;
|
|
706
912
|
const start = Date.now();
|
|
707
913
|
return withRetry(async () => {
|
|
914
|
+
const ctl = createAttemptController(options?.signal, effectiveTimeoutMs);
|
|
708
915
|
try {
|
|
709
916
|
const params = {
|
|
710
917
|
model,
|
|
@@ -715,7 +922,7 @@ function createOpenAIProvider(config) {
|
|
|
715
922
|
if (maxTokens !== void 0) params.max_tokens = maxTokens;
|
|
716
923
|
const temperature = options?.temperature ?? config.temperature;
|
|
717
924
|
if (temperature !== void 0) params.temperature = temperature;
|
|
718
|
-
const response = await client.chat.completions.create(params);
|
|
925
|
+
const response = await client.chat.completions.create(params, { signal: ctl.signal });
|
|
719
926
|
const content = response.choices.map((c) => c.message.content ?? "").join("");
|
|
720
927
|
return {
|
|
721
928
|
content,
|
|
@@ -724,13 +931,17 @@ function createOpenAIProvider(config) {
|
|
|
724
931
|
latencyMs: Date.now() - start
|
|
725
932
|
};
|
|
726
933
|
} catch (err) {
|
|
727
|
-
throw normalizeOpenAIError(err);
|
|
934
|
+
throw normalizeOpenAIError(classifyAbort(err, ctl.abortReason(), PROVIDER4));
|
|
935
|
+
} finally {
|
|
936
|
+
ctl.dispose();
|
|
728
937
|
}
|
|
729
|
-
}, retryOpts);
|
|
938
|
+
}, mergeRetryOptsWithSignal(retryOpts, options?.signal));
|
|
730
939
|
}
|
|
731
940
|
async function* stream(messages, options) {
|
|
732
941
|
const model = options?.model ?? config.model;
|
|
733
942
|
const openAIMessages = buildOpenAIMessages(messages);
|
|
943
|
+
const effectiveTimeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 3e4;
|
|
944
|
+
const stallMs = options?.streamStallTimeoutMs ?? config.streamStallTimeoutMs ?? 3e4;
|
|
734
945
|
const params = {
|
|
735
946
|
model,
|
|
736
947
|
messages: openAIMessages,
|
|
@@ -741,15 +952,17 @@ function createOpenAIProvider(config) {
|
|
|
741
952
|
if (maxTokens !== void 0) params.max_tokens = maxTokens;
|
|
742
953
|
const temperature = options?.temperature ?? config.temperature;
|
|
743
954
|
if (temperature !== void 0) params.temperature = temperature;
|
|
955
|
+
const ctl = createAttemptController(options?.signal, effectiveTimeoutMs);
|
|
744
956
|
let sdkStream;
|
|
745
957
|
try {
|
|
746
|
-
sdkStream = await client.chat.completions.create(params);
|
|
958
|
+
sdkStream = await client.chat.completions.create(params, { signal: ctl.signal });
|
|
747
959
|
} catch (err) {
|
|
748
|
-
|
|
960
|
+
ctl.dispose();
|
|
961
|
+
throw normalizeOpenAIError(classifyAbort(err, ctl.abortReason(), PROVIDER4));
|
|
749
962
|
}
|
|
750
963
|
let finalUsage;
|
|
751
964
|
try {
|
|
752
|
-
for await (const chunk of sdkStream) {
|
|
965
|
+
for await (const chunk of withStallTimeout(sdkStream, stallMs, ctl, PROVIDER4)) {
|
|
753
966
|
const delta = chunk.choices[0]?.delta.content;
|
|
754
967
|
if (delta !== void 0 && delta !== null && delta.length > 0) {
|
|
755
968
|
yield { token: delta };
|
|
@@ -759,7 +972,9 @@ function createOpenAIProvider(config) {
|
|
|
759
972
|
}
|
|
760
973
|
}
|
|
761
974
|
} catch (err) {
|
|
762
|
-
throw normalizeOpenAIError(err);
|
|
975
|
+
throw normalizeOpenAIError(classifyAbort(err, ctl.abortReason(), PROVIDER4));
|
|
976
|
+
} finally {
|
|
977
|
+
ctl.dispose();
|
|
763
978
|
}
|
|
764
979
|
if (finalUsage !== void 0) {
|
|
765
980
|
yield { token: "", usage: finalUsage };
|
|
@@ -773,8 +988,10 @@ function createOpenAIProvider(config) {
|
|
|
773
988
|
const augmentedMessages = [jsonSystemInstruction, ...messages];
|
|
774
989
|
const model = options?.model ?? config.model;
|
|
775
990
|
const openAIMessages = buildOpenAIMessages(augmentedMessages);
|
|
991
|
+
const effectiveTimeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 3e4;
|
|
776
992
|
const start = Date.now();
|
|
777
993
|
const rawResponse = await withRetry(async () => {
|
|
994
|
+
const ctl = createAttemptController(options?.signal, effectiveTimeoutMs);
|
|
778
995
|
try {
|
|
779
996
|
const params = {
|
|
780
997
|
model,
|
|
@@ -786,11 +1003,13 @@ function createOpenAIProvider(config) {
|
|
|
786
1003
|
if (maxTokens !== void 0) params.max_tokens = maxTokens;
|
|
787
1004
|
const temperature = options?.temperature ?? config.temperature;
|
|
788
1005
|
if (temperature !== void 0) params.temperature = temperature;
|
|
789
|
-
return await client.chat.completions.create(params);
|
|
1006
|
+
return await client.chat.completions.create(params, { signal: ctl.signal });
|
|
790
1007
|
} catch (err) {
|
|
791
|
-
throw normalizeOpenAIError(err);
|
|
1008
|
+
throw normalizeOpenAIError(classifyAbort(err, ctl.abortReason(), PROVIDER4));
|
|
1009
|
+
} finally {
|
|
1010
|
+
ctl.dispose();
|
|
792
1011
|
}
|
|
793
|
-
}, retryOpts);
|
|
1012
|
+
}, mergeRetryOptsWithSignal(retryOpts, options?.signal));
|
|
794
1013
|
const rawContent = rawResponse.choices[0]?.message.content ?? "";
|
|
795
1014
|
let parsed;
|
|
796
1015
|
try {
|
|
@@ -906,9 +1125,11 @@ function createPerplexityProvider(config) {
|
|
|
906
1125
|
async function complete(messages, options) {
|
|
907
1126
|
const model = options?.model ?? config.model;
|
|
908
1127
|
const chatMessages = buildMessages2(messages);
|
|
1128
|
+
const effectiveTimeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 3e4;
|
|
909
1129
|
const start = Date.now();
|
|
910
1130
|
const extraParams = extractProviderOptions(options?.providerOptions);
|
|
911
1131
|
return withRetry(async () => {
|
|
1132
|
+
const ctl = createAttemptController(options?.signal, effectiveTimeoutMs);
|
|
912
1133
|
try {
|
|
913
1134
|
const params = {
|
|
914
1135
|
model,
|
|
@@ -921,7 +1142,8 @@ function createPerplexityProvider(config) {
|
|
|
921
1142
|
const temperature = options?.temperature ?? config.temperature;
|
|
922
1143
|
if (temperature !== void 0) params.temperature = temperature;
|
|
923
1144
|
const rawResponse = await client.chat.completions.create(
|
|
924
|
-
params
|
|
1145
|
+
params,
|
|
1146
|
+
{ signal: ctl.signal }
|
|
925
1147
|
);
|
|
926
1148
|
const response = rawResponse;
|
|
927
1149
|
const content = response.choices.map((c) => c.message.content ?? "").join("");
|
|
@@ -935,13 +1157,17 @@ function createPerplexityProvider(config) {
|
|
|
935
1157
|
if (citations !== void 0) result.citations = citations;
|
|
936
1158
|
return result;
|
|
937
1159
|
} catch (err) {
|
|
938
|
-
throw normalizePerplexityError(err);
|
|
1160
|
+
throw normalizePerplexityError(classifyAbort(err, ctl.abortReason(), PROVIDER5));
|
|
1161
|
+
} finally {
|
|
1162
|
+
ctl.dispose();
|
|
939
1163
|
}
|
|
940
|
-
}, retryOpts);
|
|
1164
|
+
}, mergeRetryOptsWithSignal(retryOpts, options?.signal));
|
|
941
1165
|
}
|
|
942
1166
|
async function* stream(messages, options) {
|
|
943
1167
|
const model = options?.model ?? config.model;
|
|
944
1168
|
const chatMessages = buildMessages2(messages);
|
|
1169
|
+
const effectiveTimeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 3e4;
|
|
1170
|
+
const stallMs = options?.streamStallTimeoutMs ?? config.streamStallTimeoutMs ?? 3e4;
|
|
945
1171
|
const extraParams = extractProviderOptions(options?.providerOptions);
|
|
946
1172
|
const params = {
|
|
947
1173
|
model,
|
|
@@ -954,17 +1180,20 @@ function createPerplexityProvider(config) {
|
|
|
954
1180
|
if (maxTokens !== void 0) params.max_tokens = maxTokens;
|
|
955
1181
|
const temperature = options?.temperature ?? config.temperature;
|
|
956
1182
|
if (temperature !== void 0) params.temperature = temperature;
|
|
1183
|
+
const ctl = createAttemptController(options?.signal, effectiveTimeoutMs);
|
|
957
1184
|
let sdkStream;
|
|
958
1185
|
try {
|
|
959
1186
|
sdkStream = await client.chat.completions.create(
|
|
960
|
-
params
|
|
1187
|
+
params,
|
|
1188
|
+
{ signal: ctl.signal }
|
|
961
1189
|
);
|
|
962
1190
|
} catch (err) {
|
|
963
|
-
|
|
1191
|
+
ctl.dispose();
|
|
1192
|
+
throw normalizePerplexityError(classifyAbort(err, ctl.abortReason(), PROVIDER5));
|
|
964
1193
|
}
|
|
965
1194
|
let finalUsage;
|
|
966
1195
|
try {
|
|
967
|
-
for await (const chunk of sdkStream) {
|
|
1196
|
+
for await (const chunk of withStallTimeout(sdkStream, stallMs, ctl, PROVIDER5)) {
|
|
968
1197
|
const delta = chunk.choices[0]?.delta.content;
|
|
969
1198
|
if (delta !== void 0 && delta !== null && delta.length > 0) {
|
|
970
1199
|
yield { token: delta };
|
|
@@ -974,7 +1203,9 @@ function createPerplexityProvider(config) {
|
|
|
974
1203
|
}
|
|
975
1204
|
}
|
|
976
1205
|
} catch (err) {
|
|
977
|
-
throw normalizePerplexityError(err);
|
|
1206
|
+
throw normalizePerplexityError(classifyAbort(err, ctl.abortReason(), PROVIDER5));
|
|
1207
|
+
} finally {
|
|
1208
|
+
ctl.dispose();
|
|
978
1209
|
}
|
|
979
1210
|
if (finalUsage !== void 0) {
|
|
980
1211
|
yield { token: "", usage: finalUsage };
|
|
@@ -988,9 +1219,11 @@ function createPerplexityProvider(config) {
|
|
|
988
1219
|
const augmentedMessages = [jsonSystemInstruction, ...messages];
|
|
989
1220
|
const model = options?.model ?? config.model;
|
|
990
1221
|
const chatMessages = buildMessages2(augmentedMessages);
|
|
1222
|
+
const effectiveTimeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 3e4;
|
|
991
1223
|
const start = Date.now();
|
|
992
1224
|
const extraParams = extractProviderOptions(options?.providerOptions);
|
|
993
1225
|
const rawResponse = await withRetry(async () => {
|
|
1226
|
+
const ctl = createAttemptController(options?.signal, effectiveTimeoutMs);
|
|
994
1227
|
try {
|
|
995
1228
|
const params = {
|
|
996
1229
|
model,
|
|
@@ -1003,12 +1236,15 @@ function createPerplexityProvider(config) {
|
|
|
1003
1236
|
const temperature = options?.temperature ?? config.temperature;
|
|
1004
1237
|
if (temperature !== void 0) params.temperature = temperature;
|
|
1005
1238
|
return await client.chat.completions.create(
|
|
1006
|
-
params
|
|
1239
|
+
params,
|
|
1240
|
+
{ signal: ctl.signal }
|
|
1007
1241
|
);
|
|
1008
1242
|
} catch (err) {
|
|
1009
|
-
throw normalizePerplexityError(err);
|
|
1243
|
+
throw normalizePerplexityError(classifyAbort(err, ctl.abortReason(), PROVIDER5));
|
|
1244
|
+
} finally {
|
|
1245
|
+
ctl.dispose();
|
|
1010
1246
|
}
|
|
1011
|
-
}, retryOpts);
|
|
1247
|
+
}, mergeRetryOptsWithSignal(retryOpts, options?.signal));
|
|
1012
1248
|
const rawContent = rawResponse.choices[0]?.message.content ?? "";
|
|
1013
1249
|
let parsed;
|
|
1014
1250
|
try {
|