@jaypie/llm 1.3.10 → 1.3.11
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/dist/cjs/Llm.d.ts +19 -11
- package/dist/cjs/errors/LlmError.d.ts +62 -0
- package/dist/cjs/errors/toLlmError.d.ts +11 -0
- package/dist/cjs/index.cjs +388 -55
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +113 -14
- package/dist/cjs/index.d.ts +4 -1
- package/dist/cjs/operate/retry/RetryExecutor.d.ts +13 -0
- package/dist/cjs/operate/types.d.ts +12 -1
- package/dist/cjs/types/LlmProvider.interface.d.ts +9 -0
- package/dist/cjs/util/classifyProviderError.d.ts +13 -0
- package/dist/cjs/util/resolveModelChain.d.ts +17 -0
- package/dist/esm/Llm.d.ts +19 -11
- package/dist/esm/errors/LlmError.d.ts +62 -0
- package/dist/esm/errors/toLlmError.d.ts +11 -0
- package/dist/esm/index.d.ts +113 -14
- package/dist/esm/index.js +354 -26
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/operate/retry/RetryExecutor.d.ts +13 -0
- package/dist/esm/operate/types.d.ts +12 -1
- package/dist/esm/types/LlmProvider.interface.d.ts +9 -0
- package/dist/esm/util/classifyProviderError.d.ts +13 -0
- package/dist/esm/util/resolveModelChain.d.ts +17 -0
- package/package.json +1 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -404,6 +404,36 @@ function determineModelProvider(input) {
|
|
|
404
404
|
};
|
|
405
405
|
}
|
|
406
406
|
|
|
407
|
+
/**
|
|
408
|
+
* Normalizes a `model` option that may be a single model name or a
|
|
409
|
+
* preference-ordered array of model names.
|
|
410
|
+
*
|
|
411
|
+
* A `string[]` is interpreted as a fallback chain: index `0` is the primary
|
|
412
|
+
* model, indices `1..` become fallback entries with an auto-detected provider
|
|
413
|
+
* (reusing `determineModelProvider`, exactly as the constructor's first
|
|
414
|
+
* argument does).
|
|
415
|
+
*
|
|
416
|
+
* Returns the primary model plus the derived fallback chain. A bare string
|
|
417
|
+
* yields an empty chain and is unchanged.
|
|
418
|
+
*/
|
|
419
|
+
function resolveModelChain(model) {
|
|
420
|
+
if (!Array.isArray(model)) {
|
|
421
|
+
return { fallback: [], model };
|
|
422
|
+
}
|
|
423
|
+
if (model.length === 0) {
|
|
424
|
+
throw new errors.ConfigurationError("model array must contain at least one model name");
|
|
425
|
+
}
|
|
426
|
+
const [primary, ...rest] = model;
|
|
427
|
+
const fallback = rest.map((name) => {
|
|
428
|
+
const determined = determineModelProvider(name);
|
|
429
|
+
if (!determined.provider) {
|
|
430
|
+
throw new errors.ConfigurationError(`Unable to determine provider from model: ${name}`);
|
|
431
|
+
}
|
|
432
|
+
return { model: determined.model, provider: determined.provider };
|
|
433
|
+
});
|
|
434
|
+
return { fallback, model: primary };
|
|
435
|
+
}
|
|
436
|
+
|
|
407
437
|
//
|
|
408
438
|
//
|
|
409
439
|
// Abstract Base Adapter
|
|
@@ -1489,17 +1519,23 @@ function tryParseNumber(input, options) {
|
|
|
1489
1519
|
/**
|
|
1490
1520
|
* Categories of errors for retry logic
|
|
1491
1521
|
*/
|
|
1492
|
-
|
|
1522
|
+
exports.ErrorCategory = void 0;
|
|
1493
1523
|
(function (ErrorCategory) {
|
|
1494
1524
|
/** Error is transient and can be retried */
|
|
1495
1525
|
ErrorCategory["Retryable"] = "retryable";
|
|
1496
|
-
/** Error is due to rate limiting */
|
|
1526
|
+
/** Error is due to short-term rate limiting (retry after a delay) */
|
|
1497
1527
|
ErrorCategory["RateLimit"] = "rate_limit";
|
|
1528
|
+
/**
|
|
1529
|
+
* Provider quota is exhausted or the account cannot be billed
|
|
1530
|
+
* (insufficient funds, daily quota, plan limit). Terminal and actionable —
|
|
1531
|
+
* retrying within the request budget will not help.
|
|
1532
|
+
*/
|
|
1533
|
+
ErrorCategory["Quota"] = "quota";
|
|
1498
1534
|
/** Error cannot be recovered from */
|
|
1499
1535
|
ErrorCategory["Unrecoverable"] = "unrecoverable";
|
|
1500
1536
|
/** Error type is unknown */
|
|
1501
1537
|
ErrorCategory["Unknown"] = "unknown";
|
|
1502
|
-
})(ErrorCategory || (ErrorCategory = {}));
|
|
1538
|
+
})(exports.ErrorCategory || (exports.ErrorCategory = {}));
|
|
1503
1539
|
|
|
1504
1540
|
/**
|
|
1505
1541
|
* Transient network error detection utility.
|
|
@@ -1585,6 +1621,99 @@ function isTransientNetworkError(error) {
|
|
|
1585
1621
|
return false;
|
|
1586
1622
|
}
|
|
1587
1623
|
|
|
1624
|
+
//
|
|
1625
|
+
//
|
|
1626
|
+
// Constants
|
|
1627
|
+
//
|
|
1628
|
+
/**
|
|
1629
|
+
* Transient structured-output compile failures. The provider caches the
|
|
1630
|
+
* compiled grammar after a successful compile, so an immediate retry of the
|
|
1631
|
+
* identical request typically succeeds (issue #422).
|
|
1632
|
+
*/
|
|
1633
|
+
const RETRYABLE_MESSAGE_PATTERNS = [
|
|
1634
|
+
"grammar compilation timed out",
|
|
1635
|
+
"grammar compilation timeout",
|
|
1636
|
+
];
|
|
1637
|
+
/** Billing / insufficient-funds signals — the account cannot be charged. */
|
|
1638
|
+
const BILLING_MESSAGE_PATTERNS = [
|
|
1639
|
+
"insufficient_quota",
|
|
1640
|
+
"insufficient funds",
|
|
1641
|
+
"insufficient credit",
|
|
1642
|
+
"credit balance",
|
|
1643
|
+
"billing",
|
|
1644
|
+
"payment required",
|
|
1645
|
+
"plan and billing",
|
|
1646
|
+
];
|
|
1647
|
+
/** Exhausted usage quota (per-day / per-model limits). */
|
|
1648
|
+
const QUOTA_MESSAGE_PATTERNS = [
|
|
1649
|
+
"quota exceeded",
|
|
1650
|
+
"exceeded your current quota",
|
|
1651
|
+
"resource_exhausted",
|
|
1652
|
+
"quota_exceeded",
|
|
1653
|
+
];
|
|
1654
|
+
//
|
|
1655
|
+
//
|
|
1656
|
+
// Helpers
|
|
1657
|
+
//
|
|
1658
|
+
function extractMessage(error) {
|
|
1659
|
+
if (error instanceof Error)
|
|
1660
|
+
return error.message.toLowerCase();
|
|
1661
|
+
if (typeof error === "string")
|
|
1662
|
+
return error.toLowerCase();
|
|
1663
|
+
if (error && typeof error === "object") {
|
|
1664
|
+
const message = error.message;
|
|
1665
|
+
if (typeof message === "string")
|
|
1666
|
+
return message.toLowerCase();
|
|
1667
|
+
}
|
|
1668
|
+
return "";
|
|
1669
|
+
}
|
|
1670
|
+
//
|
|
1671
|
+
//
|
|
1672
|
+
// Main
|
|
1673
|
+
//
|
|
1674
|
+
/**
|
|
1675
|
+
* Shared, provider-agnostic first pass over an error. Returns a
|
|
1676
|
+
* {@link ClassifiedError} when the message unambiguously identifies a
|
|
1677
|
+
* cross-provider condition (retryable structured-output timeout, exhausted
|
|
1678
|
+
* quota, or a billing failure), or `undefined` to defer to the adapter's own
|
|
1679
|
+
* status/name classification.
|
|
1680
|
+
*
|
|
1681
|
+
* Adapters call this before their existing logic so that, for example, a
|
|
1682
|
+
* `429` carrying a daily-quota message is classified as {@link
|
|
1683
|
+
* ErrorCategory.Quota} rather than {@link ErrorCategory.RateLimit}.
|
|
1684
|
+
*/
|
|
1685
|
+
function classifyProviderError(error) {
|
|
1686
|
+
const message = extractMessage(error);
|
|
1687
|
+
if (!message)
|
|
1688
|
+
return undefined;
|
|
1689
|
+
for (const pattern of RETRYABLE_MESSAGE_PATTERNS) {
|
|
1690
|
+
if (message.includes(pattern)) {
|
|
1691
|
+
return { category: exports.ErrorCategory.Retryable, error, shouldRetry: true };
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
for (const pattern of BILLING_MESSAGE_PATTERNS) {
|
|
1695
|
+
if (message.includes(pattern)) {
|
|
1696
|
+
return {
|
|
1697
|
+
category: exports.ErrorCategory.Quota,
|
|
1698
|
+
error,
|
|
1699
|
+
reason: "billing",
|
|
1700
|
+
shouldRetry: false,
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
for (const pattern of QUOTA_MESSAGE_PATTERNS) {
|
|
1705
|
+
if (message.includes(pattern)) {
|
|
1706
|
+
return {
|
|
1707
|
+
category: exports.ErrorCategory.Quota,
|
|
1708
|
+
error,
|
|
1709
|
+
reason: "quota",
|
|
1710
|
+
shouldRetry: false,
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
return undefined;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1588
1717
|
//
|
|
1589
1718
|
//
|
|
1590
1719
|
// Constants
|
|
@@ -2358,12 +2487,17 @@ class AnthropicAdapter extends BaseProviderAdapter {
|
|
|
2358
2487
|
// Error Classification
|
|
2359
2488
|
//
|
|
2360
2489
|
classifyError(error) {
|
|
2490
|
+
// Shared first pass: retryable structured-output timeouts (#422),
|
|
2491
|
+
// quota exhaustion, and billing failures classify the same across providers.
|
|
2492
|
+
const shared = classifyProviderError(error);
|
|
2493
|
+
if (shared)
|
|
2494
|
+
return shared;
|
|
2361
2495
|
const errorName = error?.constructor?.name;
|
|
2362
2496
|
// Check for rate limit error
|
|
2363
2497
|
if (errorName === "RateLimitError") {
|
|
2364
2498
|
return {
|
|
2365
2499
|
error,
|
|
2366
|
-
category: ErrorCategory.RateLimit,
|
|
2500
|
+
category: exports.ErrorCategory.RateLimit,
|
|
2367
2501
|
shouldRetry: false,
|
|
2368
2502
|
suggestedDelayMs: 60000,
|
|
2369
2503
|
};
|
|
@@ -2372,7 +2506,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
|
|
|
2372
2506
|
if (RETRYABLE_ERROR_NAMES.includes(errorName)) {
|
|
2373
2507
|
return {
|
|
2374
2508
|
error,
|
|
2375
|
-
category: ErrorCategory.Retryable,
|
|
2509
|
+
category: exports.ErrorCategory.Retryable,
|
|
2376
2510
|
shouldRetry: true,
|
|
2377
2511
|
};
|
|
2378
2512
|
}
|
|
@@ -2380,7 +2514,7 @@ class AnthropicAdapter extends BaseProviderAdapter {
|
|
|
2380
2514
|
if (NOT_RETRYABLE_ERROR_NAMES.includes(errorName)) {
|
|
2381
2515
|
return {
|
|
2382
2516
|
error,
|
|
2383
|
-
category: ErrorCategory.Unrecoverable,
|
|
2517
|
+
category: exports.ErrorCategory.Unrecoverable,
|
|
2384
2518
|
shouldRetry: false,
|
|
2385
2519
|
};
|
|
2386
2520
|
}
|
|
@@ -2388,14 +2522,14 @@ class AnthropicAdapter extends BaseProviderAdapter {
|
|
|
2388
2522
|
if (isTransientNetworkError(error)) {
|
|
2389
2523
|
return {
|
|
2390
2524
|
error,
|
|
2391
|
-
category: ErrorCategory.Retryable,
|
|
2525
|
+
category: exports.ErrorCategory.Retryable,
|
|
2392
2526
|
shouldRetry: true,
|
|
2393
2527
|
};
|
|
2394
2528
|
}
|
|
2395
2529
|
// Unknown error - treat as potentially retryable
|
|
2396
2530
|
return {
|
|
2397
2531
|
error,
|
|
2398
|
-
category: ErrorCategory.Unknown,
|
|
2532
|
+
category: exports.ErrorCategory.Unknown,
|
|
2399
2533
|
shouldRetry: true,
|
|
2400
2534
|
};
|
|
2401
2535
|
}
|
|
@@ -2966,6 +3100,11 @@ class BedrockAdapter extends BaseProviderAdapter {
|
|
|
2966
3100
|
// Error Classification
|
|
2967
3101
|
//
|
|
2968
3102
|
classifyError(error) {
|
|
3103
|
+
// Shared first pass: retryable structured-output timeouts (#422),
|
|
3104
|
+
// quota exhaustion, and billing failures classify the same across providers.
|
|
3105
|
+
const shared = classifyProviderError(error);
|
|
3106
|
+
if (shared)
|
|
3107
|
+
return shared;
|
|
2969
3108
|
const errorName = error?.constructor?.name;
|
|
2970
3109
|
const errorMessage = error?.message ?? "";
|
|
2971
3110
|
if (errorName === "ThrottlingException" ||
|
|
@@ -2973,7 +3112,7 @@ class BedrockAdapter extends BaseProviderAdapter {
|
|
|
2973
3112
|
errorMessage.includes("Too Many Requests")) {
|
|
2974
3113
|
return {
|
|
2975
3114
|
error,
|
|
2976
|
-
category: ErrorCategory.RateLimit,
|
|
3115
|
+
category: exports.ErrorCategory.RateLimit,
|
|
2977
3116
|
shouldRetry: false,
|
|
2978
3117
|
suggestedDelayMs: 60000,
|
|
2979
3118
|
};
|
|
@@ -2984,7 +3123,7 @@ class BedrockAdapter extends BaseProviderAdapter {
|
|
|
2984
3123
|
errorMessage.includes("InternalServerException")) {
|
|
2985
3124
|
return {
|
|
2986
3125
|
error,
|
|
2987
|
-
category: ErrorCategory.Retryable,
|
|
3126
|
+
category: exports.ErrorCategory.Retryable,
|
|
2988
3127
|
shouldRetry: true,
|
|
2989
3128
|
};
|
|
2990
3129
|
}
|
|
@@ -2995,20 +3134,20 @@ class BedrockAdapter extends BaseProviderAdapter {
|
|
|
2995
3134
|
errorMessage.includes("ValidationException")) {
|
|
2996
3135
|
return {
|
|
2997
3136
|
error,
|
|
2998
|
-
category: ErrorCategory.Unrecoverable,
|
|
3137
|
+
category: exports.ErrorCategory.Unrecoverable,
|
|
2999
3138
|
shouldRetry: false,
|
|
3000
3139
|
};
|
|
3001
3140
|
}
|
|
3002
3141
|
if (isTransientNetworkError(error)) {
|
|
3003
3142
|
return {
|
|
3004
3143
|
error,
|
|
3005
|
-
category: ErrorCategory.Retryable,
|
|
3144
|
+
category: exports.ErrorCategory.Retryable,
|
|
3006
3145
|
shouldRetry: true,
|
|
3007
3146
|
};
|
|
3008
3147
|
}
|
|
3009
3148
|
return {
|
|
3010
3149
|
error,
|
|
3011
|
-
category: ErrorCategory.Unknown,
|
|
3150
|
+
category: exports.ErrorCategory.Unknown,
|
|
3012
3151
|
shouldRetry: true,
|
|
3013
3152
|
};
|
|
3014
3153
|
}
|
|
@@ -3635,6 +3774,11 @@ class GoogleAdapter extends BaseProviderAdapter {
|
|
|
3635
3774
|
// Error Classification
|
|
3636
3775
|
//
|
|
3637
3776
|
classifyError(error) {
|
|
3777
|
+
// Shared first pass: retryable structured-output timeouts (#422),
|
|
3778
|
+
// quota exhaustion, and billing failures classify the same across providers.
|
|
3779
|
+
const shared = classifyProviderError(error);
|
|
3780
|
+
if (shared)
|
|
3781
|
+
return shared;
|
|
3638
3782
|
const geminiError = error;
|
|
3639
3783
|
// Extract status code from error
|
|
3640
3784
|
const statusCode = geminiError.status || geminiError.code;
|
|
@@ -3642,7 +3786,7 @@ class GoogleAdapter extends BaseProviderAdapter {
|
|
|
3642
3786
|
if (statusCode === 429) {
|
|
3643
3787
|
return {
|
|
3644
3788
|
error,
|
|
3645
|
-
category: ErrorCategory.RateLimit,
|
|
3789
|
+
category: exports.ErrorCategory.RateLimit,
|
|
3646
3790
|
shouldRetry: false,
|
|
3647
3791
|
suggestedDelayMs: 60000,
|
|
3648
3792
|
};
|
|
@@ -3652,7 +3796,7 @@ class GoogleAdapter extends BaseProviderAdapter {
|
|
|
3652
3796
|
RETRYABLE_STATUS_CODES$1.includes(statusCode)) {
|
|
3653
3797
|
return {
|
|
3654
3798
|
error,
|
|
3655
|
-
category: ErrorCategory.Retryable,
|
|
3799
|
+
category: exports.ErrorCategory.Retryable,
|
|
3656
3800
|
shouldRetry: true,
|
|
3657
3801
|
};
|
|
3658
3802
|
}
|
|
@@ -3661,7 +3805,7 @@ class GoogleAdapter extends BaseProviderAdapter {
|
|
|
3661
3805
|
NOT_RETRYABLE_STATUS_CODES.includes(statusCode)) {
|
|
3662
3806
|
return {
|
|
3663
3807
|
error,
|
|
3664
|
-
category: ErrorCategory.Unrecoverable,
|
|
3808
|
+
category: exports.ErrorCategory.Unrecoverable,
|
|
3665
3809
|
shouldRetry: false,
|
|
3666
3810
|
};
|
|
3667
3811
|
}
|
|
@@ -3671,7 +3815,7 @@ class GoogleAdapter extends BaseProviderAdapter {
|
|
|
3671
3815
|
errorMessage.includes("quota exceeded")) {
|
|
3672
3816
|
return {
|
|
3673
3817
|
error,
|
|
3674
|
-
category: ErrorCategory.RateLimit,
|
|
3818
|
+
category: exports.ErrorCategory.RateLimit,
|
|
3675
3819
|
shouldRetry: false,
|
|
3676
3820
|
suggestedDelayMs: 60000,
|
|
3677
3821
|
};
|
|
@@ -3681,7 +3825,7 @@ class GoogleAdapter extends BaseProviderAdapter {
|
|
|
3681
3825
|
errorMessage.includes("ECONNREFUSED")) {
|
|
3682
3826
|
return {
|
|
3683
3827
|
error,
|
|
3684
|
-
category: ErrorCategory.Retryable,
|
|
3828
|
+
category: exports.ErrorCategory.Retryable,
|
|
3685
3829
|
shouldRetry: true,
|
|
3686
3830
|
};
|
|
3687
3831
|
}
|
|
@@ -3689,14 +3833,14 @@ class GoogleAdapter extends BaseProviderAdapter {
|
|
|
3689
3833
|
if (isTransientNetworkError(error)) {
|
|
3690
3834
|
return {
|
|
3691
3835
|
error,
|
|
3692
|
-
category: ErrorCategory.Retryable,
|
|
3836
|
+
category: exports.ErrorCategory.Retryable,
|
|
3693
3837
|
shouldRetry: true,
|
|
3694
3838
|
};
|
|
3695
3839
|
}
|
|
3696
3840
|
// Unknown error - treat as potentially retryable
|
|
3697
3841
|
return {
|
|
3698
3842
|
error,
|
|
3699
|
-
category: ErrorCategory.Unknown,
|
|
3843
|
+
category: exports.ErrorCategory.Unknown,
|
|
3700
3844
|
shouldRetry: true,
|
|
3701
3845
|
};
|
|
3702
3846
|
}
|
|
@@ -4583,11 +4727,16 @@ class OpenAiAdapter extends BaseProviderAdapter {
|
|
|
4583
4727
|
// Error Classification
|
|
4584
4728
|
//
|
|
4585
4729
|
classifyError(error) {
|
|
4730
|
+
// Shared first pass: retryable structured-output timeouts (#422),
|
|
4731
|
+
// quota exhaustion, and billing failures classify the same across providers.
|
|
4732
|
+
const shared = classifyProviderError(error);
|
|
4733
|
+
if (shared)
|
|
4734
|
+
return shared;
|
|
4586
4735
|
// Check for rate limit error
|
|
4587
4736
|
if (error instanceof RateLimitError$1) {
|
|
4588
4737
|
return {
|
|
4589
4738
|
error,
|
|
4590
|
-
category: ErrorCategory.RateLimit,
|
|
4739
|
+
category: exports.ErrorCategory.RateLimit,
|
|
4591
4740
|
shouldRetry: false, // Rate limit requires waiting, not immediate retry
|
|
4592
4741
|
suggestedDelayMs: 60000, // 1 minute default
|
|
4593
4742
|
};
|
|
@@ -4597,7 +4746,7 @@ class OpenAiAdapter extends BaseProviderAdapter {
|
|
|
4597
4746
|
if (error instanceof ErrorType) {
|
|
4598
4747
|
return {
|
|
4599
4748
|
error,
|
|
4600
|
-
category: ErrorCategory.Retryable,
|
|
4749
|
+
category: exports.ErrorCategory.Retryable,
|
|
4601
4750
|
shouldRetry: true,
|
|
4602
4751
|
};
|
|
4603
4752
|
}
|
|
@@ -4607,7 +4756,7 @@ class OpenAiAdapter extends BaseProviderAdapter {
|
|
|
4607
4756
|
if (error instanceof ErrorType) {
|
|
4608
4757
|
return {
|
|
4609
4758
|
error,
|
|
4610
|
-
category: ErrorCategory.Unrecoverable,
|
|
4759
|
+
category: exports.ErrorCategory.Unrecoverable,
|
|
4611
4760
|
shouldRetry: false,
|
|
4612
4761
|
};
|
|
4613
4762
|
}
|
|
@@ -4616,14 +4765,14 @@ class OpenAiAdapter extends BaseProviderAdapter {
|
|
|
4616
4765
|
if (isTransientNetworkError(error)) {
|
|
4617
4766
|
return {
|
|
4618
4767
|
error,
|
|
4619
|
-
category: ErrorCategory.Retryable,
|
|
4768
|
+
category: exports.ErrorCategory.Retryable,
|
|
4620
4769
|
shouldRetry: true,
|
|
4621
4770
|
};
|
|
4622
4771
|
}
|
|
4623
4772
|
// Unknown error - treat as potentially retryable
|
|
4624
4773
|
return {
|
|
4625
4774
|
error,
|
|
4626
|
-
category: ErrorCategory.Unknown,
|
|
4775
|
+
category: exports.ErrorCategory.Unknown,
|
|
4627
4776
|
shouldRetry: true,
|
|
4628
4777
|
};
|
|
4629
4778
|
}
|
|
@@ -5351,6 +5500,11 @@ class OpenRouterAdapter extends BaseProviderAdapter {
|
|
|
5351
5500
|
// Error Classification
|
|
5352
5501
|
//
|
|
5353
5502
|
classifyError(error) {
|
|
5503
|
+
// Shared first pass: retryable structured-output timeouts (#422),
|
|
5504
|
+
// quota exhaustion, and billing failures classify the same across providers.
|
|
5505
|
+
const shared = classifyProviderError(error);
|
|
5506
|
+
if (shared)
|
|
5507
|
+
return shared;
|
|
5354
5508
|
// Check if error has a status code (HTTP error)
|
|
5355
5509
|
const errorWithStatus = error;
|
|
5356
5510
|
const statusCode = errorWithStatus.status || errorWithStatus.statusCode;
|
|
@@ -5359,7 +5513,7 @@ class OpenRouterAdapter extends BaseProviderAdapter {
|
|
|
5359
5513
|
if (statusCode === RATE_LIMIT_STATUS_CODE) {
|
|
5360
5514
|
return {
|
|
5361
5515
|
error,
|
|
5362
|
-
category: ErrorCategory.RateLimit,
|
|
5516
|
+
category: exports.ErrorCategory.RateLimit,
|
|
5363
5517
|
shouldRetry: false,
|
|
5364
5518
|
suggestedDelayMs: 60000,
|
|
5365
5519
|
};
|
|
@@ -5368,7 +5522,7 @@ class OpenRouterAdapter extends BaseProviderAdapter {
|
|
|
5368
5522
|
if (RETRYABLE_STATUS_CODES.includes(statusCode)) {
|
|
5369
5523
|
return {
|
|
5370
5524
|
error,
|
|
5371
|
-
category: ErrorCategory.Retryable,
|
|
5525
|
+
category: exports.ErrorCategory.Retryable,
|
|
5372
5526
|
shouldRetry: true,
|
|
5373
5527
|
};
|
|
5374
5528
|
}
|
|
@@ -5376,7 +5530,7 @@ class OpenRouterAdapter extends BaseProviderAdapter {
|
|
|
5376
5530
|
if (statusCode >= 400 && statusCode < 500) {
|
|
5377
5531
|
return {
|
|
5378
5532
|
error,
|
|
5379
|
-
category: ErrorCategory.Unrecoverable,
|
|
5533
|
+
category: exports.ErrorCategory.Unrecoverable,
|
|
5380
5534
|
shouldRetry: false,
|
|
5381
5535
|
};
|
|
5382
5536
|
}
|
|
@@ -5387,7 +5541,7 @@ class OpenRouterAdapter extends BaseProviderAdapter {
|
|
|
5387
5541
|
errorMessage.includes("too many requests")) {
|
|
5388
5542
|
return {
|
|
5389
5543
|
error,
|
|
5390
|
-
category: ErrorCategory.RateLimit,
|
|
5544
|
+
category: exports.ErrorCategory.RateLimit,
|
|
5391
5545
|
shouldRetry: false,
|
|
5392
5546
|
suggestedDelayMs: 60000,
|
|
5393
5547
|
};
|
|
@@ -5396,14 +5550,14 @@ class OpenRouterAdapter extends BaseProviderAdapter {
|
|
|
5396
5550
|
if (isTransientNetworkError(error)) {
|
|
5397
5551
|
return {
|
|
5398
5552
|
error,
|
|
5399
|
-
category: ErrorCategory.Retryable,
|
|
5553
|
+
category: exports.ErrorCategory.Retryable,
|
|
5400
5554
|
shouldRetry: true,
|
|
5401
5555
|
};
|
|
5402
5556
|
}
|
|
5403
5557
|
// Unknown error - treat as potentially retryable
|
|
5404
5558
|
return {
|
|
5405
5559
|
error,
|
|
5406
|
-
category: ErrorCategory.Unknown,
|
|
5560
|
+
category: exports.ErrorCategory.Unknown,
|
|
5407
5561
|
shouldRetry: true,
|
|
5408
5562
|
};
|
|
5409
5563
|
}
|
|
@@ -5627,7 +5781,7 @@ class XaiAdapter extends OpenAiAdapter {
|
|
|
5627
5781
|
if (isTransientIngestError(error)) {
|
|
5628
5782
|
return {
|
|
5629
5783
|
error,
|
|
5630
|
-
category: ErrorCategory.Retryable,
|
|
5784
|
+
category: exports.ErrorCategory.Retryable,
|
|
5631
5785
|
shouldRetry: true,
|
|
5632
5786
|
};
|
|
5633
5787
|
}
|
|
@@ -6715,6 +6869,122 @@ function createStaleRejectionGuard() {
|
|
|
6715
6869
|
};
|
|
6716
6870
|
}
|
|
6717
6871
|
|
|
6872
|
+
//
|
|
6873
|
+
//
|
|
6874
|
+
// Base
|
|
6875
|
+
//
|
|
6876
|
+
/**
|
|
6877
|
+
* Normalized, provider-agnostic LLM error. Thrown by the operate/stream retry
|
|
6878
|
+
* layer when a request cannot be completed, so consumers can `catch` a stable
|
|
6879
|
+
* type regardless of which provider failed. Extends {@link JaypieError} so
|
|
6880
|
+
* `isJaypieError()` and `.status` continue to work; the original provider error
|
|
6881
|
+
* is preserved on `.cause`.
|
|
6882
|
+
*/
|
|
6883
|
+
class LlmError extends errors.JaypieError {
|
|
6884
|
+
constructor(message, category, { status = 502, title = "Bad Gateway", provider, model, retryAfterMs, cause, } = {}) {
|
|
6885
|
+
super(message, { status, title }, { _type: "LlmError" });
|
|
6886
|
+
this.name = "LlmError";
|
|
6887
|
+
this.category = category;
|
|
6888
|
+
this.provider = provider;
|
|
6889
|
+
this.model = model;
|
|
6890
|
+
this.retryAfterMs = retryAfterMs;
|
|
6891
|
+
this.cause = cause;
|
|
6892
|
+
}
|
|
6893
|
+
}
|
|
6894
|
+
//
|
|
6895
|
+
//
|
|
6896
|
+
// Subclasses
|
|
6897
|
+
//
|
|
6898
|
+
/**
|
|
6899
|
+
* Short-term rate limiting (per-minute 429). Terminal within the request
|
|
6900
|
+
* budget; `retryAfterMs` carries the provider's suggested wait when available.
|
|
6901
|
+
*/
|
|
6902
|
+
class LlmRateLimitError extends LlmError {
|
|
6903
|
+
constructor(message, options = {}) {
|
|
6904
|
+
super(message, exports.ErrorCategory.RateLimit, {
|
|
6905
|
+
status: 429,
|
|
6906
|
+
title: "Too Many Requests",
|
|
6907
|
+
...options,
|
|
6908
|
+
});
|
|
6909
|
+
this.name = "LlmRateLimitError";
|
|
6910
|
+
}
|
|
6911
|
+
}
|
|
6912
|
+
/**
|
|
6913
|
+
* Provider quota is exhausted or the account cannot be billed. Terminal and
|
|
6914
|
+
* actionable. `reason` distinguishes an exhausted usage quota from insufficient
|
|
6915
|
+
* funds.
|
|
6916
|
+
*/
|
|
6917
|
+
class LlmQuotaError extends LlmError {
|
|
6918
|
+
constructor(message, { reason = "quota", ...options } = {}) {
|
|
6919
|
+
super(message, exports.ErrorCategory.Quota, {
|
|
6920
|
+
status: 402,
|
|
6921
|
+
title: "Payment Required",
|
|
6922
|
+
...options,
|
|
6923
|
+
});
|
|
6924
|
+
this.name = "LlmQuotaError";
|
|
6925
|
+
this.reason = reason;
|
|
6926
|
+
}
|
|
6927
|
+
}
|
|
6928
|
+
/**
|
|
6929
|
+
* The request cannot be recovered from (bad request, authentication,
|
|
6930
|
+
* permission, not found). Terminal.
|
|
6931
|
+
*/
|
|
6932
|
+
class LlmUnrecoverableError extends LlmError {
|
|
6933
|
+
constructor(message, options = {}) {
|
|
6934
|
+
super(message, exports.ErrorCategory.Unrecoverable, {
|
|
6935
|
+
status: 502,
|
|
6936
|
+
title: "Bad Gateway",
|
|
6937
|
+
...options,
|
|
6938
|
+
});
|
|
6939
|
+
this.name = "LlmUnrecoverableError";
|
|
6940
|
+
}
|
|
6941
|
+
}
|
|
6942
|
+
/**
|
|
6943
|
+
* A transient or unknown error survived the retry budget. Terminal only because
|
|
6944
|
+
* retries were exhausted; the underlying condition may succeed later.
|
|
6945
|
+
*/
|
|
6946
|
+
class LlmTransientError extends LlmError {
|
|
6947
|
+
constructor(message, options = {}) {
|
|
6948
|
+
super(message, exports.ErrorCategory.Retryable, {
|
|
6949
|
+
status: 504,
|
|
6950
|
+
title: "Gateway Timeout",
|
|
6951
|
+
...options,
|
|
6952
|
+
});
|
|
6953
|
+
this.name = "LlmTransientError";
|
|
6954
|
+
}
|
|
6955
|
+
}
|
|
6956
|
+
|
|
6957
|
+
/**
|
|
6958
|
+
* Map a {@link ClassifiedError} to the matching {@link LlmError} subclass,
|
|
6959
|
+
* preserving the original error as `cause`. Used by the retry layer to throw a
|
|
6960
|
+
* stable, catchable type instead of a bare gateway error.
|
|
6961
|
+
*/
|
|
6962
|
+
function toLlmError(classified, context = {}) {
|
|
6963
|
+
const original = classified.error;
|
|
6964
|
+
const message = original instanceof Error ? original.message : String(original);
|
|
6965
|
+
const options = {
|
|
6966
|
+
cause: original,
|
|
6967
|
+
model: context.model,
|
|
6968
|
+
provider: context.provider,
|
|
6969
|
+
retryAfterMs: classified.suggestedDelayMs,
|
|
6970
|
+
};
|
|
6971
|
+
switch (classified.category) {
|
|
6972
|
+
case exports.ErrorCategory.RateLimit:
|
|
6973
|
+
return new LlmRateLimitError(message, options);
|
|
6974
|
+
case exports.ErrorCategory.Quota:
|
|
6975
|
+
return new LlmQuotaError(message, {
|
|
6976
|
+
...options,
|
|
6977
|
+
reason: classified.reason ?? "quota",
|
|
6978
|
+
});
|
|
6979
|
+
case exports.ErrorCategory.Unrecoverable:
|
|
6980
|
+
return new LlmUnrecoverableError(message, options);
|
|
6981
|
+
case exports.ErrorCategory.Retryable:
|
|
6982
|
+
case exports.ErrorCategory.Unknown:
|
|
6983
|
+
default:
|
|
6984
|
+
return new LlmTransientError(message, options);
|
|
6985
|
+
}
|
|
6986
|
+
}
|
|
6987
|
+
|
|
6718
6988
|
//
|
|
6719
6989
|
//
|
|
6720
6990
|
// Main
|
|
@@ -6729,6 +6999,18 @@ class RetryExecutor {
|
|
|
6729
6999
|
this.hookRunner = config.hookRunner ?? hookRunner;
|
|
6730
7000
|
this.errorClassifier = config.errorClassifier;
|
|
6731
7001
|
}
|
|
7002
|
+
/**
|
|
7003
|
+
* Build the typed, provider-agnostic error thrown when a request cannot be
|
|
7004
|
+
* completed — classified (rate limit / quota / unrecoverable / transient)
|
|
7005
|
+
* and carrying the provider, model, and original error as `cause`.
|
|
7006
|
+
*/
|
|
7007
|
+
toTerminalError(error, context) {
|
|
7008
|
+
const classified = this.errorClassifier.classify(error);
|
|
7009
|
+
return toLlmError(classified, {
|
|
7010
|
+
model: context.model,
|
|
7011
|
+
provider: context.provider,
|
|
7012
|
+
});
|
|
7013
|
+
}
|
|
6732
7014
|
/**
|
|
6733
7015
|
* Execute an operation with retry logic.
|
|
6734
7016
|
* Each attempt receives an AbortSignal. On failure, the signal is aborted
|
|
@@ -6772,8 +7054,7 @@ class RetryExecutor {
|
|
|
6772
7054
|
providerRequest: options.context.providerRequest,
|
|
6773
7055
|
error,
|
|
6774
7056
|
});
|
|
6775
|
-
|
|
6776
|
-
throw new errors.BadGatewayError(errorMessage);
|
|
7057
|
+
throw this.toTerminalError(error, options.context);
|
|
6777
7058
|
}
|
|
6778
7059
|
// Check if error is not retryable
|
|
6779
7060
|
if (!this.errorClassifier.isRetryable(error)) {
|
|
@@ -6785,8 +7066,7 @@ class RetryExecutor {
|
|
|
6785
7066
|
providerRequest: options.context.providerRequest,
|
|
6786
7067
|
error,
|
|
6787
7068
|
});
|
|
6788
|
-
|
|
6789
|
-
throw new errors.BadGatewayError(errorMessage);
|
|
7069
|
+
throw this.toTerminalError(error, options.context);
|
|
6790
7070
|
}
|
|
6791
7071
|
// Warn if this is an unknown error type
|
|
6792
7072
|
if (!this.errorClassifier.isKnownError(error)) {
|
|
@@ -6829,6 +7109,7 @@ const MAX_CONSECUTIVE_TOOL_ERRORS = 6;
|
|
|
6829
7109
|
*/
|
|
6830
7110
|
function createErrorClassifier(adapter) {
|
|
6831
7111
|
return {
|
|
7112
|
+
classify: (error) => adapter.classifyError(error),
|
|
6832
7113
|
isRetryable: (error) => adapter.isRetryableError(error),
|
|
6833
7114
|
isKnownError: (error) => {
|
|
6834
7115
|
const classified = adapter.classifyError(error);
|
|
@@ -7082,7 +7363,9 @@ class OperateLoop {
|
|
|
7082
7363
|
const response = await retryExecutor.execute((signal) => this.adapter.executeRequest(this.client, providerRequest, signal), {
|
|
7083
7364
|
context: {
|
|
7084
7365
|
input: state.currentInput,
|
|
7366
|
+
model: options.model ?? this.adapter.defaultModel,
|
|
7085
7367
|
options,
|
|
7368
|
+
provider: this.adapter.name,
|
|
7086
7369
|
providerRequest,
|
|
7087
7370
|
},
|
|
7088
7371
|
hooks: hooksWithProgress,
|
|
@@ -7705,9 +7988,11 @@ class StreamLoop {
|
|
|
7705
7988
|
!this.adapter.isRetryableError(error)) {
|
|
7706
7989
|
log.error(`Stream request failed after ${this.retryPolicy.maxRetries} retries`);
|
|
7707
7990
|
log.var({ error });
|
|
7708
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7709
7991
|
llmSpan?.finish();
|
|
7710
|
-
throw
|
|
7992
|
+
throw toLlmError(this.adapter.classifyError(error), {
|
|
7993
|
+
model: options.model ?? this.adapter.defaultModel,
|
|
7994
|
+
provider: this.adapter.name,
|
|
7995
|
+
});
|
|
7711
7996
|
}
|
|
7712
7997
|
const delay = this.retryPolicy.getDelayForAttempt(attempt);
|
|
7713
7998
|
log.warn(`Stream request failed. Retrying in ${delay}ms...`);
|
|
@@ -9178,7 +9463,14 @@ class XaiProvider {
|
|
|
9178
9463
|
|
|
9179
9464
|
class Llm {
|
|
9180
9465
|
constructor(providerName = DEFAULT.PROVIDER.NAME, options = {}) {
|
|
9181
|
-
const { fallback, model } = options;
|
|
9466
|
+
const { fallback: fallbackOption, model: modelOption } = options;
|
|
9467
|
+
// A `model` array is sugar for a preference-ordered fallback chain:
|
|
9468
|
+
// index 0 is primary, the rest become fallback entries (provider
|
|
9469
|
+
// auto-detected). Any explicit `fallback` is appended after the chain.
|
|
9470
|
+
const { fallback: modelFallback, model } = resolveModelChain(modelOption);
|
|
9471
|
+
const fallback = modelFallback.length || fallbackOption
|
|
9472
|
+
? [...modelFallback, ...(fallbackOption ?? [])]
|
|
9473
|
+
: undefined;
|
|
9182
9474
|
let finalProvider = providerName;
|
|
9183
9475
|
let finalModel = model;
|
|
9184
9476
|
// Legacy: accept "gemini" but warn
|
|
@@ -9250,6 +9542,11 @@ class Llm {
|
|
|
9250
9542
|
}
|
|
9251
9543
|
}
|
|
9252
9544
|
async send(message, options) {
|
|
9545
|
+
if (options && Array.isArray(options.model)) {
|
|
9546
|
+
// `send` has no fallback machinery; use the primary model only
|
|
9547
|
+
const { model } = resolveModelChain(options.model);
|
|
9548
|
+
return this._llm.send(message, { ...options, model });
|
|
9549
|
+
}
|
|
9253
9550
|
return this._llm.send(message, options);
|
|
9254
9551
|
}
|
|
9255
9552
|
/**
|
|
@@ -9282,8 +9579,21 @@ class Llm {
|
|
|
9282
9579
|
if (!this._llm.operate) {
|
|
9283
9580
|
throw new errors.NotImplementedError(`Provider ${this._provider} does not support operate method`);
|
|
9284
9581
|
}
|
|
9285
|
-
|
|
9286
|
-
|
|
9582
|
+
// A per-call `model` array becomes primary + a derived fallback chain
|
|
9583
|
+
// prepended to any resolved chain.
|
|
9584
|
+
const { fallback: modelFallback, model: perCallModel } = resolveModelChain(options.model);
|
|
9585
|
+
const resolvedOptions = {
|
|
9586
|
+
...options,
|
|
9587
|
+
model: perCallModel,
|
|
9588
|
+
};
|
|
9589
|
+
const fallbackChain = [
|
|
9590
|
+
...modelFallback,
|
|
9591
|
+
...this.resolveFallbackChain(resolvedOptions),
|
|
9592
|
+
];
|
|
9593
|
+
const optionsWithoutFallback = {
|
|
9594
|
+
...resolvedOptions,
|
|
9595
|
+
fallback: false,
|
|
9596
|
+
};
|
|
9287
9597
|
let lastError;
|
|
9288
9598
|
let attempts = 0;
|
|
9289
9599
|
// Try primary provider first
|
|
@@ -9332,33 +9642,45 @@ class Llm {
|
|
|
9332
9642
|
if (!this._llm.stream) {
|
|
9333
9643
|
throw new errors.NotImplementedError(`Provider ${this._provider} does not support stream method`);
|
|
9334
9644
|
}
|
|
9335
|
-
|
|
9645
|
+
// `stream` has no instance-level fallback machinery; an array model uses
|
|
9646
|
+
// the primary and ignores the rest.
|
|
9647
|
+
const { model } = resolveModelChain(options.model);
|
|
9648
|
+
const streamOptions = { ...options, model };
|
|
9649
|
+
yield* this._llm.stream(input, streamOptions);
|
|
9336
9650
|
}
|
|
9337
9651
|
static async send(message, options) {
|
|
9338
9652
|
const { llm, apiKey, model, ...messageOptions } = options || {};
|
|
9339
|
-
|
|
9653
|
+
// A `model` array derives a fallback chain; `send` has no fallback
|
|
9654
|
+
// machinery, so the primary model is used and the chain is ignored.
|
|
9655
|
+
const { model: primaryModel } = resolveModelChain(model);
|
|
9656
|
+
const instance = new Llm(llm, { apiKey, model: primaryModel });
|
|
9340
9657
|
return instance.send(message, messageOptions);
|
|
9341
9658
|
}
|
|
9342
9659
|
static async operate(input, options) {
|
|
9343
9660
|
const { apiKey, fallback, llm, model, ...operateOptions } = options || {};
|
|
9661
|
+
// A `model` array becomes primary + derived fallback chain
|
|
9662
|
+
const { fallback: modelFallback, model: primaryModel } = resolveModelChain(model);
|
|
9344
9663
|
let finalLlm = llm;
|
|
9345
|
-
let finalModel =
|
|
9346
|
-
if (!llm &&
|
|
9347
|
-
const determined = determineModelProvider(
|
|
9664
|
+
let finalModel = primaryModel;
|
|
9665
|
+
if (!llm && primaryModel) {
|
|
9666
|
+
const determined = determineModelProvider(primaryModel);
|
|
9348
9667
|
if (determined.provider) {
|
|
9349
9668
|
finalLlm = determined.provider;
|
|
9350
9669
|
}
|
|
9351
9670
|
}
|
|
9352
|
-
else if (llm &&
|
|
9671
|
+
else if (llm && primaryModel) {
|
|
9353
9672
|
// When both llm and model are provided, check if they conflict
|
|
9354
|
-
const determined = determineModelProvider(
|
|
9673
|
+
const determined = determineModelProvider(primaryModel);
|
|
9355
9674
|
if (determined.provider && determined.provider !== llm) {
|
|
9356
9675
|
// Don't pass the conflicting model to the constructor
|
|
9357
9676
|
finalModel = undefined;
|
|
9358
9677
|
}
|
|
9359
9678
|
}
|
|
9360
9679
|
// Resolve fallback for static method: pass to instance if array, pass to operate options if false
|
|
9361
|
-
const
|
|
9680
|
+
const explicitFallback = Array.isArray(fallback) ? fallback : [];
|
|
9681
|
+
const instanceFallback = modelFallback.length || explicitFallback.length
|
|
9682
|
+
? [...modelFallback, ...explicitFallback]
|
|
9683
|
+
: undefined;
|
|
9362
9684
|
const operateFallback = fallback === false ? false : undefined;
|
|
9363
9685
|
const instance = new Llm(finalLlm, {
|
|
9364
9686
|
apiKey,
|
|
@@ -9372,23 +9694,29 @@ class Llm {
|
|
|
9372
9694
|
}
|
|
9373
9695
|
static stream(input, options) {
|
|
9374
9696
|
const { llm, apiKey, model, ...streamOptions } = options || {};
|
|
9697
|
+
// A `model` array becomes primary + derived fallback chain
|
|
9698
|
+
const { fallback: modelFallback, model: primaryModel } = resolveModelChain(model);
|
|
9375
9699
|
let finalLlm = llm;
|
|
9376
|
-
let finalModel =
|
|
9377
|
-
if (!llm &&
|
|
9378
|
-
const determined = determineModelProvider(
|
|
9700
|
+
let finalModel = primaryModel;
|
|
9701
|
+
if (!llm && primaryModel) {
|
|
9702
|
+
const determined = determineModelProvider(primaryModel);
|
|
9379
9703
|
if (determined.provider) {
|
|
9380
9704
|
finalLlm = determined.provider;
|
|
9381
9705
|
}
|
|
9382
9706
|
}
|
|
9383
|
-
else if (llm &&
|
|
9707
|
+
else if (llm && primaryModel) {
|
|
9384
9708
|
// When both llm and model are provided, check if they conflict
|
|
9385
|
-
const determined = determineModelProvider(
|
|
9709
|
+
const determined = determineModelProvider(primaryModel);
|
|
9386
9710
|
if (determined.provider && determined.provider !== llm) {
|
|
9387
9711
|
// Don't pass the conflicting model to the constructor
|
|
9388
9712
|
finalModel = undefined;
|
|
9389
9713
|
}
|
|
9390
9714
|
}
|
|
9391
|
-
const instance = new Llm(finalLlm, {
|
|
9715
|
+
const instance = new Llm(finalLlm, {
|
|
9716
|
+
apiKey,
|
|
9717
|
+
fallback: modelFallback.length ? modelFallback : undefined,
|
|
9718
|
+
model: finalModel,
|
|
9719
|
+
});
|
|
9392
9720
|
return instance.stream(input, streamOptions);
|
|
9393
9721
|
}
|
|
9394
9722
|
}
|
|
@@ -9692,6 +10020,11 @@ exports.GoogleProvider = GoogleProvider;
|
|
|
9692
10020
|
exports.JaypieToolkit = JaypieToolkit;
|
|
9693
10021
|
exports.LLM = constants;
|
|
9694
10022
|
exports.Llm = Llm;
|
|
10023
|
+
exports.LlmError = LlmError;
|
|
10024
|
+
exports.LlmQuotaError = LlmQuotaError;
|
|
10025
|
+
exports.LlmRateLimitError = LlmRateLimitError;
|
|
10026
|
+
exports.LlmTransientError = LlmTransientError;
|
|
10027
|
+
exports.LlmUnrecoverableError = LlmUnrecoverableError;
|
|
9695
10028
|
exports.OpenRouterProvider = OpenRouterProvider;
|
|
9696
10029
|
exports.Toolkit = Toolkit;
|
|
9697
10030
|
exports.XaiProvider = XaiProvider;
|