@absolutejs/ai 0.6.4 → 0.6.6
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/CHANGELOG.md +19 -0
- package/README.md +18 -0
- package/changelog.json +37 -0
- package/dist/ai/index.js +359 -188
- package/dist/ai/index.js.map +17 -16
- package/dist/ai/providers/anthropic.js +147 -104
- package/dist/ai/providers/anthropic.js.map +8 -8
- package/dist/ai/providers/gemini.js +146 -103
- package/dist/ai/providers/gemini.js.map +7 -7
- package/dist/ai/providers/ollama.js +61 -20
- package/dist/ai/providers/ollama.js.map +5 -5
- package/dist/ai/providers/openai.js +245 -151
- package/dist/ai/providers/openai.js.map +11 -10
- package/dist/ai/providers/openaiCompatible.js +245 -151
- package/dist/ai/providers/openaiCompatible.js.map +11 -10
- package/dist/ai/providers/openaiResponses.js +243 -149
- package/dist/ai/providers/openaiResponses.js.map +10 -9
- package/dist/ai/providers/openrouter.js +246 -152
- package/dist/ai/providers/openrouter.js.map +12 -11
- package/dist/src/ai/contextPolicy.d.ts +2 -0
- package/dist/src/ai/errors/providerError.d.ts +2 -0
- package/dist/src/ai/index.d.ts +3 -0
- package/dist/src/ai/inputCapacity.d.ts +4 -2
- package/dist/src/ai/prepareTextInput.d.ts +2 -0
- package/dist/src/ai/providerFallback.d.ts +27 -8
- package/dist/src/ai/providers/openaiSchema.d.ts +4 -0
- package/package.json +3 -3
|
@@ -223,89 +223,6 @@ var geminiThinkingConfig = (model, reasoning) => {
|
|
|
223
223
|
return { thinkingBudget: reasoning.budgetTokens ?? budgets[effort] };
|
|
224
224
|
};
|
|
225
225
|
|
|
226
|
-
// src/ai/inputCapacity.ts
|
|
227
|
-
class AIInputError extends Error {
|
|
228
|
-
code;
|
|
229
|
-
providerStatus;
|
|
230
|
-
requestId;
|
|
231
|
-
constructor(code, message, providerStatus, requestId) {
|
|
232
|
-
super(message);
|
|
233
|
-
this.code = code;
|
|
234
|
-
this.providerStatus = providerStatus;
|
|
235
|
-
this.requestId = requestId;
|
|
236
|
-
this.name = "AIInputError";
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
var positiveTokenLimit = (value) => {
|
|
240
|
-
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0)
|
|
241
|
-
throw new AIInputError("capacity_unavailable", "The provider did not supply a valid model limit.");
|
|
242
|
-
return value;
|
|
243
|
-
};
|
|
244
|
-
var inputTokenCount = (value) => {
|
|
245
|
-
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
246
|
-
throw new AIInputError("capacity_unavailable", "The provider did not supply a valid token count.");
|
|
247
|
-
return value;
|
|
248
|
-
};
|
|
249
|
-
var inspectAIInput = async (provider, params) => {
|
|
250
|
-
params.signal?.throwIfAborted();
|
|
251
|
-
if (!provider.inputCapacity)
|
|
252
|
-
throw new AIInputError("capacity_unavailable", "This provider does not expose model capacity and token counting.");
|
|
253
|
-
const [limits, tokens] = await Promise.all([
|
|
254
|
-
provider.inputCapacity.getLimits(params),
|
|
255
|
-
provider.inputCapacity.countTokens(params)
|
|
256
|
-
]).catch((error) => {
|
|
257
|
-
if (error instanceof Error && error.name === "AIInputError" && "code" in error && (error.code === "capacity_unavailable" || error.code === "input_too_large" || error.code === "unsupported_content"))
|
|
258
|
-
throw new AIInputError(error.code, error.message, "providerStatus" in error && typeof error.providerStatus === "number" ? error.providerStatus : undefined, "requestId" in error && typeof error.requestId === "string" ? error.requestId : undefined);
|
|
259
|
-
throw error;
|
|
260
|
-
});
|
|
261
|
-
const inputTokens = inputTokenCount(tokens);
|
|
262
|
-
const outputTokens = positiveTokenLimit(provider.inputCapacity.outputTokens?.(params) ?? params.maxTokens ?? limits.maxOutputTokens);
|
|
263
|
-
const maxInputTokens = positiveTokenLimit(limits.maxInputTokens);
|
|
264
|
-
const maxOutputTokens = positiveTokenLimit(limits.maxOutputTokens);
|
|
265
|
-
const availableInputTokens = Math.max(0, Math.min(maxInputTokens, limits.contextWindowTokens === undefined ? maxInputTokens : positiveTokenLimit(limits.contextWindowTokens) - outputTokens));
|
|
266
|
-
return {
|
|
267
|
-
limits,
|
|
268
|
-
inputTokens,
|
|
269
|
-
inputTokenScope: provider.inputCapacity.countScope?.(params) ?? "request",
|
|
270
|
-
outputTokens,
|
|
271
|
-
availableInputTokens,
|
|
272
|
-
fits: outputTokens <= maxOutputTokens && inputTokens <= availableInputTokens
|
|
273
|
-
};
|
|
274
|
-
};
|
|
275
|
-
var cacheModelLimits = (load, ttlMs = 300000) => {
|
|
276
|
-
const cache = new Map;
|
|
277
|
-
return async (params) => {
|
|
278
|
-
const entry = cache.get(params.model);
|
|
279
|
-
if (entry && Date.now() - entry.at < ttlMs)
|
|
280
|
-
return entry.limits;
|
|
281
|
-
const limits = await load(params);
|
|
282
|
-
positiveTokenLimit(limits.maxInputTokens);
|
|
283
|
-
positiveTokenLimit(limits.maxOutputTokens);
|
|
284
|
-
if (cache.size >= 100)
|
|
285
|
-
cache.clear();
|
|
286
|
-
cache.set(params.model, { at: Date.now(), limits });
|
|
287
|
-
return limits;
|
|
288
|
-
};
|
|
289
|
-
};
|
|
290
|
-
var capacityJson = async (response) => {
|
|
291
|
-
if (!response.ok) {
|
|
292
|
-
const payload = await response.json().catch(() => {
|
|
293
|
-
return;
|
|
294
|
-
});
|
|
295
|
-
const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : undefined;
|
|
296
|
-
const providerError = record?.error;
|
|
297
|
-
const nested = providerError && typeof providerError === "object" && !Array.isArray(providerError) ? providerError : undefined;
|
|
298
|
-
const message = nested?.message ?? record?.message;
|
|
299
|
-
const detail = typeof message === "string" ? message.slice(0, 1000) : undefined;
|
|
300
|
-
const id = response.headers.get("request-id") ?? response.headers.get("x-request-id") ?? record?.request_id;
|
|
301
|
-
throw new AIInputError("capacity_unavailable", `Model capacity request failed (${response.status}).${detail ? ` ${detail}` : ""}`, response.status, typeof id === "string" ? id.slice(0, 200) : undefined);
|
|
302
|
-
}
|
|
303
|
-
const value = await response.json();
|
|
304
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
305
|
-
throw new AIInputError("capacity_unavailable", "Model capacity response was invalid.");
|
|
306
|
-
return value;
|
|
307
|
-
};
|
|
308
|
-
|
|
309
226
|
// src/ai/errors/providerError.ts
|
|
310
227
|
var PROVIDER_STATUS_PAGES = {
|
|
311
228
|
anthropic: "https://status.claude.com",
|
|
@@ -365,7 +282,7 @@ class ProviderError extends Error {
|
|
|
365
282
|
this.provider = init.provider;
|
|
366
283
|
this.status = init.status ?? null;
|
|
367
284
|
this.type = init.type ?? (typeof init.metadata?.code === "string" ? init.metadata.code : typeof init.metadata?.type === "string" ? init.metadata.type : null);
|
|
368
|
-
this.billingUnavailable = BILLING_ERROR_TYPES.has(this.type ?? "") || init.status === 402;
|
|
285
|
+
this.billingUnavailable = BILLING_ERROR_TYPES.has(this.type ?? "") || init.status === 402 || init.provider === "anthropic" && init.status === 400 && /credit balance is too low/i.test(String(init.metadata?.message ?? init.message));
|
|
369
286
|
this.notSent = init.notSent === true;
|
|
370
287
|
this.retryable = !this.billingUnavailable && init.retryable;
|
|
371
288
|
this.metadata = init.metadata;
|
|
@@ -391,6 +308,17 @@ class ProviderError extends Error {
|
|
|
391
308
|
static from(err, provider) {
|
|
392
309
|
if (err instanceof ProviderError)
|
|
393
310
|
return err;
|
|
311
|
+
if (isProviderError(err))
|
|
312
|
+
return new ProviderError({
|
|
313
|
+
provider: err.provider,
|
|
314
|
+
message: err.message,
|
|
315
|
+
status: err.status,
|
|
316
|
+
type: err.type,
|
|
317
|
+
retryable: err.retryable,
|
|
318
|
+
notSent: err.notSent,
|
|
319
|
+
metadata: err.metadata,
|
|
320
|
+
cause: err
|
|
321
|
+
});
|
|
394
322
|
if (isAbortError(err))
|
|
395
323
|
throw err;
|
|
396
324
|
const rawMessage = err instanceof Error ? err.message : String(err ?? "");
|
|
@@ -408,6 +336,92 @@ class ProviderError extends Error {
|
|
|
408
336
|
}
|
|
409
337
|
}
|
|
410
338
|
var capitalize = (value) => value.charAt(0).toUpperCase() + value.slice(1);
|
|
339
|
+
var isProviderError = (error) => error instanceof Error && error.name === "ProviderError" && ("provider" in error) && typeof error.provider === "string" && ("status" in error) && (error.status === null || typeof error.status === "number") && ("retryable" in error) && typeof error.retryable === "boolean" && ("billingUnavailable" in error) && typeof error.billingUnavailable === "boolean" && ("notSent" in error) && typeof error.notSent === "boolean";
|
|
340
|
+
|
|
341
|
+
// src/ai/inputCapacity.ts
|
|
342
|
+
class AIInputError extends Error {
|
|
343
|
+
code;
|
|
344
|
+
providerStatus;
|
|
345
|
+
requestId;
|
|
346
|
+
providerError;
|
|
347
|
+
constructor(code, message, providerStatus, requestId, providerError) {
|
|
348
|
+
super(message);
|
|
349
|
+
this.code = code;
|
|
350
|
+
this.providerStatus = providerStatus;
|
|
351
|
+
this.requestId = requestId;
|
|
352
|
+
this.providerError = providerError;
|
|
353
|
+
this.name = "AIInputError";
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
var positiveTokenLimit = (value) => {
|
|
357
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0)
|
|
358
|
+
throw new AIInputError("capacity_unavailable", "The provider did not supply a valid model limit.");
|
|
359
|
+
return value;
|
|
360
|
+
};
|
|
361
|
+
var inputTokenCount = (value) => {
|
|
362
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
363
|
+
throw new AIInputError("capacity_unavailable", "The provider did not supply a valid token count.");
|
|
364
|
+
return value;
|
|
365
|
+
};
|
|
366
|
+
var inspectAIInput = async (provider, params) => {
|
|
367
|
+
params.signal?.throwIfAborted();
|
|
368
|
+
if (!provider.inputCapacity)
|
|
369
|
+
throw new AIInputError("capacity_unavailable", "This provider does not expose model capacity and token counting.");
|
|
370
|
+
const [limits, tokens] = await Promise.all([
|
|
371
|
+
provider.inputCapacity.getLimits(params),
|
|
372
|
+
provider.inputCapacity.countTokens(params)
|
|
373
|
+
]).catch((error) => {
|
|
374
|
+
if (error instanceof Error && error.name === "AIInputError" && "code" in error && (error.code === "capacity_unavailable" || error.code === "input_too_large" || error.code === "unsupported_content"))
|
|
375
|
+
throw new AIInputError(error.code, error.message, "providerStatus" in error && typeof error.providerStatus === "number" ? error.providerStatus : undefined, "requestId" in error && typeof error.requestId === "string" ? error.requestId : undefined, "providerError" in error && isProviderError(error.providerError) ? ProviderError.from(error.providerError, error.providerError.provider) : undefined);
|
|
376
|
+
throw error;
|
|
377
|
+
});
|
|
378
|
+
const inputTokens = inputTokenCount(tokens);
|
|
379
|
+
const outputTokens = positiveTokenLimit(provider.inputCapacity.outputTokens?.(params) ?? params.maxTokens ?? limits.maxOutputTokens);
|
|
380
|
+
const maxInputTokens = positiveTokenLimit(limits.maxInputTokens);
|
|
381
|
+
const maxOutputTokens = positiveTokenLimit(limits.maxOutputTokens);
|
|
382
|
+
const availableInputTokens = Math.max(0, Math.min(maxInputTokens, limits.contextWindowTokens === undefined ? maxInputTokens : positiveTokenLimit(limits.contextWindowTokens) - outputTokens));
|
|
383
|
+
return {
|
|
384
|
+
limits,
|
|
385
|
+
inputTokens,
|
|
386
|
+
inputTokenScope: provider.inputCapacity.countScope?.(params) ?? "request",
|
|
387
|
+
outputTokens,
|
|
388
|
+
availableInputTokens,
|
|
389
|
+
fits: outputTokens <= maxOutputTokens && inputTokens <= availableInputTokens
|
|
390
|
+
};
|
|
391
|
+
};
|
|
392
|
+
var cacheModelLimits = (load, ttlMs = 300000) => {
|
|
393
|
+
const cache = new Map;
|
|
394
|
+
return async (params) => {
|
|
395
|
+
const entry = cache.get(params.model);
|
|
396
|
+
if (entry && Date.now() - entry.at < ttlMs)
|
|
397
|
+
return entry.limits;
|
|
398
|
+
const limits = await load(params);
|
|
399
|
+
positiveTokenLimit(limits.maxInputTokens);
|
|
400
|
+
positiveTokenLimit(limits.maxOutputTokens);
|
|
401
|
+
if (cache.size >= 100)
|
|
402
|
+
cache.clear();
|
|
403
|
+
cache.set(params.model, { at: Date.now(), limits });
|
|
404
|
+
return limits;
|
|
405
|
+
};
|
|
406
|
+
};
|
|
407
|
+
var capacityJson = async (response, provider = "unknown") => {
|
|
408
|
+
if (!response.ok) {
|
|
409
|
+
const payload = await response.json().catch(() => {
|
|
410
|
+
return;
|
|
411
|
+
});
|
|
412
|
+
const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : undefined;
|
|
413
|
+
const providerError = record?.error;
|
|
414
|
+
const nested = providerError && typeof providerError === "object" && !Array.isArray(providerError) ? providerError : undefined;
|
|
415
|
+
const message = nested?.message ?? record?.message;
|
|
416
|
+
const detail = typeof message === "string" ? message.slice(0, 1000) : undefined;
|
|
417
|
+
const id = response.headers.get("request-id") ?? response.headers.get("x-request-id") ?? record?.request_id;
|
|
418
|
+
throw new AIInputError("capacity_unavailable", `Model capacity request failed (${response.status}).${detail ? ` ${detail}` : ""}`, response.status, typeof id === "string" ? id.slice(0, 200) : undefined, ProviderError.fromResponse(provider, response.status, JSON.stringify({ error: nested ?? record ?? {} })));
|
|
419
|
+
}
|
|
420
|
+
const value = await response.json();
|
|
421
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
422
|
+
throw new AIInputError("capacity_unavailable", "Model capacity response was invalid.");
|
|
423
|
+
return value;
|
|
424
|
+
};
|
|
411
425
|
|
|
412
426
|
// src/ai/resilience.ts
|
|
413
427
|
var DEFAULT_CONFIG = {
|
|
@@ -538,6 +552,29 @@ var withResilience = (provider, providerName = "unknown") => {
|
|
|
538
552
|
let billingFailure = null;
|
|
539
553
|
let billingRetryAt = 0;
|
|
540
554
|
let billingProbe = false;
|
|
555
|
+
const inspectAvailable = async (inspect) => {
|
|
556
|
+
if (billingFailure && Date.now() < billingRetryAt) {
|
|
557
|
+
throw new ProviderError({
|
|
558
|
+
provider: providerName,
|
|
559
|
+
message: "Provider billing is unavailable",
|
|
560
|
+
type: billingFailure.type,
|
|
561
|
+
status: billingFailure.status,
|
|
562
|
+
metadata: billingFailure.metadata,
|
|
563
|
+
retryable: false,
|
|
564
|
+
notSent: true
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
try {
|
|
568
|
+
return await inspect();
|
|
569
|
+
} catch (error) {
|
|
570
|
+
const nested = error instanceof Error && "providerError" in error ? error.providerError : error;
|
|
571
|
+
if (isProviderError(nested) && nested.billingUnavailable) {
|
|
572
|
+
billingFailure = ProviderError.from(nested, providerName);
|
|
573
|
+
billingRetryAt = Date.now() + billingCooldownMs;
|
|
574
|
+
}
|
|
575
|
+
throw error;
|
|
576
|
+
}
|
|
577
|
+
};
|
|
541
578
|
const billingCooldownMs = 5 * 60000;
|
|
542
579
|
const attempt = async function* (params, attemptNo) {
|
|
543
580
|
let yielded = false;
|
|
@@ -565,33 +602,39 @@ var withResilience = (provider, providerName = "unknown") => {
|
|
|
565
602
|
if (providerError.retryable)
|
|
566
603
|
noteFailure(providerName, providerError);
|
|
567
604
|
throw providerError;
|
|
605
|
+
} finally {
|
|
606
|
+
billingProbe = false;
|
|
568
607
|
}
|
|
569
608
|
};
|
|
570
609
|
return {
|
|
571
610
|
...provider,
|
|
572
|
-
|
|
611
|
+
...provider.inputCapacity ? {
|
|
612
|
+
inputCapacity: {
|
|
613
|
+
...provider.inputCapacity,
|
|
614
|
+
getLimits: (params) => inspectAvailable(() => provider.inputCapacity.getLimits(params)),
|
|
615
|
+
countTokens: (params) => inspectAvailable(() => provider.inputCapacity.countTokens(params))
|
|
616
|
+
}
|
|
617
|
+
} : {},
|
|
618
|
+
stream: async function* (params) {
|
|
573
619
|
if (billingFailure && (Date.now() < billingRetryAt || billingProbe)) {
|
|
574
620
|
const failure = billingFailure;
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
}();
|
|
621
|
+
throw new ProviderError({
|
|
622
|
+
provider: providerName,
|
|
623
|
+
message: "Provider billing is unavailable",
|
|
624
|
+
type: failure.type,
|
|
625
|
+
status: failure.status,
|
|
626
|
+
metadata: failure.metadata,
|
|
627
|
+
retryable: false,
|
|
628
|
+
notSent: true
|
|
629
|
+
});
|
|
585
630
|
}
|
|
586
|
-
if (billingFailure)
|
|
587
|
-
billingProbe = true;
|
|
588
631
|
const tripped = circuitOpen(providerName);
|
|
589
632
|
if (tripped) {
|
|
590
|
-
|
|
591
|
-
throw tripped;
|
|
592
|
-
}();
|
|
633
|
+
throw tripped;
|
|
593
634
|
}
|
|
594
|
-
|
|
635
|
+
if (billingFailure)
|
|
636
|
+
billingProbe = true;
|
|
637
|
+
yield* attempt(params, 0);
|
|
595
638
|
}
|
|
596
639
|
};
|
|
597
640
|
};
|
|
@@ -600,7 +643,7 @@ var withResilience = (provider, providerName = "unknown") => {
|
|
|
600
643
|
var instrumentAIProvider = (provider, providerName) => {
|
|
601
644
|
const resilient = withResilience(provider, providerName);
|
|
602
645
|
return {
|
|
603
|
-
...
|
|
646
|
+
...resilient,
|
|
604
647
|
stream: (params) => {
|
|
605
648
|
if (!params.onUsage && !params.onSpan) {
|
|
606
649
|
return resilient.stream(params);
|
|
@@ -999,5 +1042,5 @@ export {
|
|
|
999
1042
|
gemini2 as gemini
|
|
1000
1043
|
};
|
|
1001
1044
|
|
|
1002
|
-
//# debugId=
|
|
1045
|
+
//# debugId=E6B87AD1F8EC179264756E2164756E21
|
|
1003
1046
|
//# sourceMappingURL=gemini.js.map
|