@blockrun/clawrouter 0.12.94 → 0.12.96
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/auth.d.ts +82 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +388 -0
- package/dist/auth.js.map +1 -0
- package/dist/balance.d.ts +97 -0
- package/dist/balance.d.ts.map +1 -0
- package/dist/balance.js +157 -0
- package/dist/balance.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +256 -54991
- package/dist/cli.js.map +1 -1
- package/dist/compression/codebook.d.ts +24 -0
- package/dist/compression/codebook.d.ts.map +1 -0
- package/dist/compression/codebook.js +119 -0
- package/dist/compression/codebook.js.map +1 -0
- package/dist/compression/index.d.ts +33 -0
- package/dist/compression/index.d.ts.map +1 -0
- package/dist/compression/index.js +246 -0
- package/dist/compression/index.js.map +1 -0
- package/dist/compression/layers/deduplication.d.ts +28 -0
- package/dist/compression/layers/deduplication.d.ts.map +1 -0
- package/dist/compression/layers/deduplication.js +101 -0
- package/dist/compression/layers/deduplication.js.map +1 -0
- package/dist/compression/layers/dictionary.d.ts +21 -0
- package/dist/compression/layers/dictionary.d.ts.map +1 -0
- package/dist/compression/layers/dictionary.js +73 -0
- package/dist/compression/layers/dictionary.js.map +1 -0
- package/dist/compression/layers/dynamic-codebook.d.ts +26 -0
- package/dist/compression/layers/dynamic-codebook.d.ts.map +1 -0
- package/dist/compression/layers/dynamic-codebook.js +149 -0
- package/dist/compression/layers/dynamic-codebook.js.map +1 -0
- package/dist/compression/layers/json-compact.d.ts +23 -0
- package/dist/compression/layers/json-compact.d.ts.map +1 -0
- package/dist/compression/layers/json-compact.js +79 -0
- package/dist/compression/layers/json-compact.js.map +1 -0
- package/dist/compression/layers/observation.d.ts +21 -0
- package/dist/compression/layers/observation.d.ts.map +1 -0
- package/dist/compression/layers/observation.js +130 -0
- package/dist/compression/layers/observation.js.map +1 -0
- package/dist/compression/layers/paths.d.ts +24 -0
- package/dist/compression/layers/paths.d.ts.map +1 -0
- package/dist/compression/layers/paths.js +110 -0
- package/dist/compression/layers/paths.js.map +1 -0
- package/dist/compression/layers/whitespace.d.ts +27 -0
- package/dist/compression/layers/whitespace.d.ts.map +1 -0
- package/dist/compression/layers/whitespace.js +60 -0
- package/dist/compression/layers/whitespace.js.map +1 -0
- package/dist/compression/types.d.ts +84 -0
- package/dist/compression/types.d.ts.map +1 -0
- package/dist/compression/types.js +27 -0
- package/dist/compression/types.js.map +1 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +22 -0
- package/dist/config.js.map +1 -0
- package/dist/dedup.d.ts +34 -0
- package/dist/dedup.d.ts.map +1 -0
- package/dist/dedup.js +150 -0
- package/dist/dedup.js.map +1 -0
- package/dist/doctor.d.ts +8 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +375 -0
- package/dist/doctor.js.map +1 -0
- package/dist/error-classification.test.d.ts +2 -0
- package/dist/error-classification.test.d.ts.map +1 -0
- package/dist/error-classification.test.js +59 -0
- package/dist/error-classification.test.js.map +1 -0
- package/dist/errors.d.ts +54 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +85 -0
- package/dist/errors.js.map +1 -0
- package/dist/exclude-models.d.ts +28 -0
- package/dist/exclude-models.d.ts.map +1 -0
- package/dist/exclude-models.integration.test.d.ts +2 -0
- package/dist/exclude-models.integration.test.d.ts.map +1 -0
- package/dist/exclude-models.integration.test.js +46 -0
- package/dist/exclude-models.integration.test.js.map +1 -0
- package/dist/exclude-models.js +70 -0
- package/dist/exclude-models.js.map +1 -0
- package/dist/exclude-models.test.d.ts +5 -0
- package/dist/exclude-models.test.d.ts.map +1 -0
- package/dist/exclude-models.test.js +95 -0
- package/dist/exclude-models.test.js.map +1 -0
- package/dist/fs-read.d.ts +11 -0
- package/dist/fs-read.d.ts.map +1 -0
- package/dist/fs-read.js +47 -0
- package/dist/fs-read.js.map +1 -0
- package/dist/index.d.ts +34 -1368
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1071 -56191
- package/dist/index.js.map +1 -1
- package/dist/journal.d.ts +67 -0
- package/dist/journal.d.ts.map +1 -0
- package/dist/journal.js +184 -0
- package/dist/journal.js.map +1 -0
- package/dist/journal.test.d.ts +2 -0
- package/dist/journal.test.d.ts.map +1 -0
- package/dist/journal.test.js +201 -0
- package/dist/journal.test.js.map +1 -0
- package/dist/logger.d.ts +33 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +35 -0
- package/dist/logger.js.map +1 -0
- package/dist/models.d.ts +107 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +941 -0
- package/dist/models.js.map +1 -0
- package/dist/models.test.d.ts +2 -0
- package/dist/models.test.d.ts.map +1 -0
- package/dist/models.test.js +38 -0
- package/dist/models.test.js.map +1 -0
- package/dist/partners/index.d.ts +8 -0
- package/dist/partners/index.d.ts.map +1 -0
- package/dist/partners/index.js +6 -0
- package/dist/partners/index.js.map +1 -0
- package/dist/partners/registry.d.ts +51 -0
- package/dist/partners/registry.d.ts.map +1 -0
- package/dist/partners/registry.js +256 -0
- package/dist/partners/registry.js.map +1 -0
- package/dist/partners/tools.d.ts +24 -0
- package/dist/partners/tools.d.ts.map +1 -0
- package/dist/partners/tools.js +97 -0
- package/dist/partners/tools.js.map +1 -0
- package/dist/payment-preauth.d.ts +17 -0
- package/dist/payment-preauth.d.ts.map +1 -0
- package/dist/payment-preauth.js +102 -0
- package/dist/payment-preauth.js.map +1 -0
- package/dist/provider.d.ts +19 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +45 -0
- package/dist/provider.js.map +1 -0
- package/dist/proxy.d.ts +210 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.debrand.test.d.ts +2 -0
- package/dist/proxy.debrand.test.d.ts.map +1 -0
- package/dist/proxy.debrand.test.js +101 -0
- package/dist/proxy.debrand.test.js.map +1 -0
- package/dist/proxy.degraded-response.test.d.ts +2 -0
- package/dist/proxy.degraded-response.test.d.ts.map +1 -0
- package/dist/proxy.degraded-response.test.js +105 -0
- package/dist/proxy.degraded-response.test.js.map +1 -0
- package/dist/proxy.image-download.test.d.ts +2 -0
- package/dist/proxy.image-download.test.d.ts.map +1 -0
- package/dist/proxy.image-download.test.js +92 -0
- package/dist/proxy.image-download.test.js.map +1 -0
- package/dist/proxy.js +3810 -0
- package/dist/proxy.js.map +1 -0
- package/dist/proxy.models-endpoint.test.d.ts +2 -0
- package/dist/proxy.models-endpoint.test.d.ts.map +1 -0
- package/dist/proxy.models-endpoint.test.js +19 -0
- package/dist/proxy.models-endpoint.test.js.map +1 -0
- package/dist/proxy.multiturn-thinking.test.d.ts +2 -0
- package/dist/proxy.multiturn-thinking.test.d.ts.map +1 -0
- package/dist/proxy.multiturn-thinking.test.js +82 -0
- package/dist/proxy.multiturn-thinking.test.js.map +1 -0
- package/dist/proxy.payment-chain-reuse.test.d.ts +2 -0
- package/dist/proxy.payment-chain-reuse.test.d.ts.map +1 -0
- package/dist/proxy.payment-chain-reuse.test.js +30 -0
- package/dist/proxy.payment-chain-reuse.test.js.map +1 -0
- package/dist/proxy.solana-resilience.test.d.ts +12 -0
- package/dist/proxy.solana-resilience.test.d.ts.map +1 -0
- package/dist/proxy.solana-resilience.test.js +39 -0
- package/dist/proxy.solana-resilience.test.js.map +1 -0
- package/dist/report.d.ts +6 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +44 -0
- package/dist/report.js.map +1 -0
- package/dist/response-cache.advanced.test.d.ts +2 -0
- package/dist/response-cache.advanced.test.d.ts.map +1 -0
- package/dist/response-cache.advanced.test.js +566 -0
- package/dist/response-cache.advanced.test.js.map +1 -0
- package/dist/response-cache.d.ts +85 -0
- package/dist/response-cache.d.ts.map +1 -0
- package/dist/response-cache.edge-cases.test.d.ts +2 -0
- package/dist/response-cache.edge-cases.test.d.ts.map +1 -0
- package/dist/response-cache.edge-cases.test.js +488 -0
- package/dist/response-cache.edge-cases.test.js.map +1 -0
- package/dist/response-cache.extreme.test.d.ts +2 -0
- package/dist/response-cache.extreme.test.d.ts.map +1 -0
- package/dist/response-cache.extreme.test.js +561 -0
- package/dist/response-cache.extreme.test.js.map +1 -0
- package/dist/response-cache.js +240 -0
- package/dist/response-cache.js.map +1 -0
- package/dist/response-cache.test.d.ts +2 -0
- package/dist/response-cache.test.d.ts.map +1 -0
- package/dist/response-cache.test.js +242 -0
- package/dist/response-cache.test.js.map +1 -0
- package/dist/retry.d.ts +42 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +99 -0
- package/dist/retry.js.map +1 -0
- package/dist/router/config.d.ts +11 -0
- package/dist/router/config.d.ts.map +1 -0
- package/dist/router/config.js +1215 -0
- package/dist/router/config.js.map +1 -0
- package/dist/router/index.d.ts +18 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +19 -0
- package/dist/router/index.js.map +1 -0
- package/dist/router/llm-classifier.d.ts +29 -0
- package/dist/router/llm-classifier.d.ts.map +1 -0
- package/dist/router/llm-classifier.js +99 -0
- package/dist/router/llm-classifier.js.map +1 -0
- package/dist/router/rules.d.ts +12 -0
- package/dist/router/rules.d.ts.map +1 -0
- package/dist/router/rules.js +202 -0
- package/dist/router/rules.js.map +1 -0
- package/dist/router/selector.d.ts +58 -0
- package/dist/router/selector.d.ts.map +1 -0
- package/dist/router/selector.js +165 -0
- package/dist/router/selector.js.map +1 -0
- package/dist/router/selector.test.d.ts +2 -0
- package/dist/router/selector.test.d.ts.map +1 -0
- package/dist/router/selector.test.js +67 -0
- package/dist/router/selector.test.js.map +1 -0
- package/dist/router/strategy.d.ts +19 -0
- package/dist/router/strategy.d.ts.map +1 -0
- package/dist/router/strategy.js +138 -0
- package/dist/router/strategy.js.map +1 -0
- package/dist/router/strategy.test.d.ts +2 -0
- package/dist/router/strategy.test.d.ts.map +1 -0
- package/dist/router/strategy.test.js +131 -0
- package/dist/router/strategy.test.js.map +1 -0
- package/dist/router/types.d.ts +127 -0
- package/dist/router/types.d.ts.map +1 -0
- package/dist/router/types.js +11 -0
- package/dist/router/types.js.map +1 -0
- package/dist/session.d.ts +124 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +256 -0
- package/dist/session.js.map +1 -0
- package/dist/session.test.d.ts +2 -0
- package/dist/session.test.d.ts.map +1 -0
- package/dist/session.test.js +163 -0
- package/dist/session.test.js.map +1 -0
- package/dist/solana-balance.d.ts +48 -0
- package/dist/solana-balance.d.ts.map +1 -0
- package/dist/solana-balance.js +142 -0
- package/dist/solana-balance.js.map +1 -0
- package/dist/spend-control.d.ts +112 -0
- package/dist/spend-control.d.ts.map +1 -0
- package/dist/spend-control.js +278 -0
- package/dist/spend-control.js.map +1 -0
- package/dist/spend-control.test.d.ts +5 -0
- package/dist/spend-control.test.d.ts.map +1 -0
- package/dist/spend-control.test.js +210 -0
- package/dist/spend-control.test.js.map +1 -0
- package/dist/stats.d.ts +64 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +295 -0
- package/dist/stats.js.map +1 -0
- package/dist/types.d.ts +150 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/update-hint.test.d.ts +9 -0
- package/dist/update-hint.test.d.ts.map +1 -0
- package/dist/update-hint.test.js +156 -0
- package/dist/update-hint.test.js.map +1 -0
- package/dist/updater.d.ts +10 -0
- package/dist/updater.d.ts.map +1 -0
- package/dist/updater.js +55 -0
- package/dist/updater.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +16 -0
- package/dist/version.js.map +1 -0
- package/dist/wallet.d.ts +52 -0
- package/dist/wallet.d.ts.map +1 -0
- package/dist/wallet.js +93 -0
- package/dist/wallet.js.map +1 -0
- package/dist/wallet.test.d.ts +6 -0
- package/dist/wallet.test.d.ts.map +1 -0
- package/dist/wallet.test.js +117 -0
- package/dist/wallet.test.js.map +1 -0
- package/dist/x402-sdk.test.d.ts +6 -0
- package/dist/x402-sdk.test.d.ts.map +1 -0
- package/dist/x402-sdk.test.js +161 -0
- package/dist/x402-sdk.test.js.map +1 -0
- package/package.json +2 -2
- package/scripts/e2e-multiturn.mjs +4 -1
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response Cache for LLM Completions
|
|
3
|
+
*
|
|
4
|
+
* Caches LLM responses by request hash (model + messages + params).
|
|
5
|
+
* Inspired by LiteLLM's caching system. Returns cached responses for
|
|
6
|
+
* identical requests, saving both cost and latency.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - TTL-based expiration (default 10 minutes)
|
|
10
|
+
* - LRU eviction when cache is full
|
|
11
|
+
* - Size limits per item (1MB max)
|
|
12
|
+
* - Heap-based expiration tracking for efficient pruning
|
|
13
|
+
*/
|
|
14
|
+
import { createHash } from "node:crypto";
|
|
15
|
+
const DEFAULT_CONFIG = {
|
|
16
|
+
maxSize: 200,
|
|
17
|
+
defaultTTL: 600,
|
|
18
|
+
maxItemSize: 1_048_576, // 1MB
|
|
19
|
+
enabled: true,
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Canonicalize JSON by sorting object keys recursively.
|
|
23
|
+
* Ensures identical logical content produces identical hash.
|
|
24
|
+
*/
|
|
25
|
+
function canonicalize(obj) {
|
|
26
|
+
if (obj === null || typeof obj !== "object") {
|
|
27
|
+
return obj;
|
|
28
|
+
}
|
|
29
|
+
if (Array.isArray(obj)) {
|
|
30
|
+
return obj.map(canonicalize);
|
|
31
|
+
}
|
|
32
|
+
const sorted = {};
|
|
33
|
+
for (const key of Object.keys(obj).sort()) {
|
|
34
|
+
sorted[key] = canonicalize(obj[key]);
|
|
35
|
+
}
|
|
36
|
+
return sorted;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Strip fields that shouldn't affect cache key:
|
|
40
|
+
* - stream (we handle streaming separately)
|
|
41
|
+
* - timestamps injected by OpenClaw
|
|
42
|
+
* - request IDs
|
|
43
|
+
*/
|
|
44
|
+
const TIMESTAMP_PATTERN = /^\[\w{3}\s+\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}\s+\w+\]\s*/;
|
|
45
|
+
function normalizeForCache(obj) {
|
|
46
|
+
const result = {};
|
|
47
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
48
|
+
// Skip fields that don't affect response content
|
|
49
|
+
if (["stream", "user", "request_id", "x-request-id"].includes(key)) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (key === "messages" && Array.isArray(value)) {
|
|
53
|
+
// Strip timestamps from message content
|
|
54
|
+
result[key] = value.map((msg) => {
|
|
55
|
+
if (typeof msg === "object" && msg !== null) {
|
|
56
|
+
const m = msg;
|
|
57
|
+
if (typeof m.content === "string") {
|
|
58
|
+
return { ...m, content: m.content.replace(TIMESTAMP_PATTERN, "") };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return msg;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
result[key] = value;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
export class ResponseCache {
|
|
71
|
+
cache = new Map();
|
|
72
|
+
expirationHeap = [];
|
|
73
|
+
config;
|
|
74
|
+
// Stats for monitoring
|
|
75
|
+
stats = {
|
|
76
|
+
hits: 0,
|
|
77
|
+
misses: 0,
|
|
78
|
+
evictions: 0,
|
|
79
|
+
};
|
|
80
|
+
constructor(config = {}) {
|
|
81
|
+
// Filter out undefined values so they don't override defaults
|
|
82
|
+
const filtered = Object.fromEntries(Object.entries(config).filter(([, v]) => v !== undefined));
|
|
83
|
+
this.config = { ...DEFAULT_CONFIG, ...filtered };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Generate cache key from request body.
|
|
87
|
+
* Hashes: model + messages + temperature + max_tokens + other params
|
|
88
|
+
*/
|
|
89
|
+
static generateKey(body) {
|
|
90
|
+
try {
|
|
91
|
+
const parsed = JSON.parse(typeof body === "string" ? body : body.toString());
|
|
92
|
+
const normalized = normalizeForCache(parsed);
|
|
93
|
+
const canonical = canonicalize(normalized);
|
|
94
|
+
const keyContent = JSON.stringify(canonical);
|
|
95
|
+
return createHash("sha256").update(keyContent).digest("hex").slice(0, 32);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Fallback: hash raw body
|
|
99
|
+
const content = typeof body === "string" ? body : body.toString();
|
|
100
|
+
return createHash("sha256").update(content).digest("hex").slice(0, 32);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Check if caching is enabled for this request.
|
|
105
|
+
* Respects cache control headers and request params.
|
|
106
|
+
*/
|
|
107
|
+
shouldCache(body, headers) {
|
|
108
|
+
if (!this.config.enabled)
|
|
109
|
+
return false;
|
|
110
|
+
// Respect Cache-Control: no-cache header
|
|
111
|
+
if (headers?.["cache-control"]?.includes("no-cache")) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
// Check for explicit cache disable in body
|
|
115
|
+
try {
|
|
116
|
+
const parsed = JSON.parse(typeof body === "string" ? body : body.toString());
|
|
117
|
+
if (parsed.cache === false || parsed.no_cache === true) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Not JSON, allow caching
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get cached response if available and not expired.
|
|
128
|
+
*/
|
|
129
|
+
get(key) {
|
|
130
|
+
const entry = this.cache.get(key);
|
|
131
|
+
if (!entry) {
|
|
132
|
+
this.stats.misses++;
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
// Check expiration
|
|
136
|
+
if (Date.now() > entry.expiresAt) {
|
|
137
|
+
this.cache.delete(key);
|
|
138
|
+
this.stats.misses++;
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
this.stats.hits++;
|
|
142
|
+
return entry;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Cache a response with optional custom TTL.
|
|
146
|
+
*/
|
|
147
|
+
set(key, response, ttlSeconds) {
|
|
148
|
+
// Don't cache if disabled or maxSize is 0
|
|
149
|
+
if (!this.config.enabled || this.config.maxSize <= 0)
|
|
150
|
+
return;
|
|
151
|
+
// Don't cache if item too large
|
|
152
|
+
if (response.body.length > this.config.maxItemSize) {
|
|
153
|
+
console.log(`[ResponseCache] Skipping cache - item too large: ${response.body.length} bytes`);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
// Don't cache error responses
|
|
157
|
+
if (response.status >= 400) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
// Evict if at capacity
|
|
161
|
+
if (this.cache.size >= this.config.maxSize) {
|
|
162
|
+
this.evict();
|
|
163
|
+
}
|
|
164
|
+
const now = Date.now();
|
|
165
|
+
const ttl = ttlSeconds ?? this.config.defaultTTL;
|
|
166
|
+
const expiresAt = now + ttl * 1000;
|
|
167
|
+
const entry = {
|
|
168
|
+
...response,
|
|
169
|
+
cachedAt: now,
|
|
170
|
+
expiresAt,
|
|
171
|
+
};
|
|
172
|
+
this.cache.set(key, entry);
|
|
173
|
+
this.expirationHeap.push({ expiresAt, key });
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Evict expired and oldest entries to make room.
|
|
177
|
+
*/
|
|
178
|
+
evict() {
|
|
179
|
+
const now = Date.now();
|
|
180
|
+
// First pass: remove expired entries
|
|
181
|
+
this.expirationHeap.sort((a, b) => a.expiresAt - b.expiresAt);
|
|
182
|
+
while (this.expirationHeap.length > 0) {
|
|
183
|
+
const oldest = this.expirationHeap[0];
|
|
184
|
+
// Check if entry still exists and matches
|
|
185
|
+
const entry = this.cache.get(oldest.key);
|
|
186
|
+
if (!entry || entry.expiresAt !== oldest.expiresAt) {
|
|
187
|
+
// Stale heap entry, remove it
|
|
188
|
+
this.expirationHeap.shift();
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (oldest.expiresAt <= now) {
|
|
192
|
+
// Expired, remove both
|
|
193
|
+
this.cache.delete(oldest.key);
|
|
194
|
+
this.expirationHeap.shift();
|
|
195
|
+
this.stats.evictions++;
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
// Not expired, stop
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// Second pass: if still at capacity, evict oldest
|
|
203
|
+
while (this.cache.size >= this.config.maxSize && this.expirationHeap.length > 0) {
|
|
204
|
+
const oldest = this.expirationHeap.shift();
|
|
205
|
+
if (this.cache.has(oldest.key)) {
|
|
206
|
+
this.cache.delete(oldest.key);
|
|
207
|
+
this.stats.evictions++;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Get cache statistics.
|
|
213
|
+
*/
|
|
214
|
+
getStats() {
|
|
215
|
+
const total = this.stats.hits + this.stats.misses;
|
|
216
|
+
const hitRate = total > 0 ? ((this.stats.hits / total) * 100).toFixed(1) + "%" : "0%";
|
|
217
|
+
return {
|
|
218
|
+
size: this.cache.size,
|
|
219
|
+
maxSize: this.config.maxSize,
|
|
220
|
+
hits: this.stats.hits,
|
|
221
|
+
misses: this.stats.misses,
|
|
222
|
+
evictions: this.stats.evictions,
|
|
223
|
+
hitRate,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Clear all cached entries.
|
|
228
|
+
*/
|
|
229
|
+
clear() {
|
|
230
|
+
this.cache.clear();
|
|
231
|
+
this.expirationHeap = [];
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Check if cache is enabled.
|
|
235
|
+
*/
|
|
236
|
+
isEnabled() {
|
|
237
|
+
return this.config.enabled;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=response-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response-cache.js","sourceRoot":"","sources":["../src/response-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response-cache.test.d.ts","sourceRoot":"","sources":["../src/response-cache.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
2
|
+
import { ResponseCache } from "./response-cache.js";
|
|
3
|
+
describe("ResponseCache", () => {
|
|
4
|
+
let cache;
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
cache = new ResponseCache();
|
|
7
|
+
});
|
|
8
|
+
describe("generateKey", () => {
|
|
9
|
+
it("should generate consistent keys for identical requests", () => {
|
|
10
|
+
const body1 = JSON.stringify({
|
|
11
|
+
model: "gpt-4",
|
|
12
|
+
messages: [{ role: "user", content: "hello" }],
|
|
13
|
+
});
|
|
14
|
+
const body2 = JSON.stringify({
|
|
15
|
+
model: "gpt-4",
|
|
16
|
+
messages: [{ role: "user", content: "hello" }],
|
|
17
|
+
});
|
|
18
|
+
expect(ResponseCache.generateKey(body1)).toBe(ResponseCache.generateKey(body2));
|
|
19
|
+
});
|
|
20
|
+
it("should generate different keys for different messages", () => {
|
|
21
|
+
const body1 = JSON.stringify({
|
|
22
|
+
model: "gpt-4",
|
|
23
|
+
messages: [{ role: "user", content: "hello" }],
|
|
24
|
+
});
|
|
25
|
+
const body2 = JSON.stringify({
|
|
26
|
+
model: "gpt-4",
|
|
27
|
+
messages: [{ role: "user", content: "goodbye" }],
|
|
28
|
+
});
|
|
29
|
+
expect(ResponseCache.generateKey(body1)).not.toBe(ResponseCache.generateKey(body2));
|
|
30
|
+
});
|
|
31
|
+
it("should generate different keys for different models", () => {
|
|
32
|
+
const body1 = JSON.stringify({
|
|
33
|
+
model: "gpt-4",
|
|
34
|
+
messages: [{ role: "user", content: "hello" }],
|
|
35
|
+
});
|
|
36
|
+
const body2 = JSON.stringify({
|
|
37
|
+
model: "gpt-3.5-turbo",
|
|
38
|
+
messages: [{ role: "user", content: "hello" }],
|
|
39
|
+
});
|
|
40
|
+
expect(ResponseCache.generateKey(body1)).not.toBe(ResponseCache.generateKey(body2));
|
|
41
|
+
});
|
|
42
|
+
it("should ignore stream parameter for cache key", () => {
|
|
43
|
+
const body1 = JSON.stringify({
|
|
44
|
+
model: "gpt-4",
|
|
45
|
+
messages: [{ role: "user", content: "hello" }],
|
|
46
|
+
stream: true,
|
|
47
|
+
});
|
|
48
|
+
const body2 = JSON.stringify({
|
|
49
|
+
model: "gpt-4",
|
|
50
|
+
messages: [{ role: "user", content: "hello" }],
|
|
51
|
+
stream: false,
|
|
52
|
+
});
|
|
53
|
+
expect(ResponseCache.generateKey(body1)).toBe(ResponseCache.generateKey(body2));
|
|
54
|
+
});
|
|
55
|
+
it("should strip timestamps from messages", () => {
|
|
56
|
+
const body1 = JSON.stringify({
|
|
57
|
+
model: "gpt-4",
|
|
58
|
+
messages: [{ role: "user", content: "[Mon 2024-01-15 10:30 PST] hello" }],
|
|
59
|
+
});
|
|
60
|
+
const body2 = JSON.stringify({
|
|
61
|
+
model: "gpt-4",
|
|
62
|
+
messages: [{ role: "user", content: "hello" }],
|
|
63
|
+
});
|
|
64
|
+
expect(ResponseCache.generateKey(body1)).toBe(ResponseCache.generateKey(body2));
|
|
65
|
+
});
|
|
66
|
+
it("should handle Buffer input", () => {
|
|
67
|
+
const body = Buffer.from(JSON.stringify({
|
|
68
|
+
model: "gpt-4",
|
|
69
|
+
messages: [{ role: "user", content: "hello" }],
|
|
70
|
+
}));
|
|
71
|
+
expect(ResponseCache.generateKey(body)).toHaveLength(32);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe("shouldCache", () => {
|
|
75
|
+
it("should return true by default", () => {
|
|
76
|
+
const body = JSON.stringify({ model: "gpt-4", messages: [] });
|
|
77
|
+
expect(cache.shouldCache(body)).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
it("should return false when Cache-Control: no-cache header is present", () => {
|
|
80
|
+
const body = JSON.stringify({ model: "gpt-4", messages: [] });
|
|
81
|
+
expect(cache.shouldCache(body, { "cache-control": "no-cache" })).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
it("should return true for other Cache-Control values", () => {
|
|
84
|
+
const body = JSON.stringify({ model: "gpt-4", messages: [] });
|
|
85
|
+
expect(cache.shouldCache(body, { "cache-control": "max-age=300" })).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
it("should return false when cache=false in body", () => {
|
|
88
|
+
const body = JSON.stringify({ model: "gpt-4", messages: [], cache: false });
|
|
89
|
+
expect(cache.shouldCache(body)).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
it("should return false when no_cache=true in body", () => {
|
|
92
|
+
const body = JSON.stringify({ model: "gpt-4", messages: [], no_cache: true });
|
|
93
|
+
expect(cache.shouldCache(body)).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
it("should return false when cache is disabled", () => {
|
|
96
|
+
const disabledCache = new ResponseCache({ enabled: false });
|
|
97
|
+
const body = JSON.stringify({ model: "gpt-4", messages: [] });
|
|
98
|
+
expect(disabledCache.shouldCache(body)).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe("get/set", () => {
|
|
102
|
+
it("should return undefined for non-existent key", () => {
|
|
103
|
+
expect(cache.get("nonexistent")).toBeUndefined();
|
|
104
|
+
});
|
|
105
|
+
it("should store and retrieve cached response", () => {
|
|
106
|
+
const key = "test-key";
|
|
107
|
+
const response = {
|
|
108
|
+
body: Buffer.from('{"result": "ok"}'),
|
|
109
|
+
status: 200,
|
|
110
|
+
headers: { "content-type": "application/json" },
|
|
111
|
+
model: "gpt-4",
|
|
112
|
+
};
|
|
113
|
+
cache.set(key, response);
|
|
114
|
+
const cached = cache.get(key);
|
|
115
|
+
expect(cached).toBeDefined();
|
|
116
|
+
expect(cached.body.toString()).toBe('{"result": "ok"}');
|
|
117
|
+
expect(cached.status).toBe(200);
|
|
118
|
+
expect(cached.model).toBe("gpt-4");
|
|
119
|
+
});
|
|
120
|
+
it("should not cache error responses (status >= 400)", () => {
|
|
121
|
+
const key = "error-key";
|
|
122
|
+
const response = {
|
|
123
|
+
body: Buffer.from('{"error": "bad request"}'),
|
|
124
|
+
status: 400,
|
|
125
|
+
headers: {},
|
|
126
|
+
model: "gpt-4",
|
|
127
|
+
};
|
|
128
|
+
cache.set(key, response);
|
|
129
|
+
expect(cache.get(key)).toBeUndefined();
|
|
130
|
+
});
|
|
131
|
+
it("should not cache items exceeding maxItemSize", () => {
|
|
132
|
+
const smallCache = new ResponseCache({ maxItemSize: 1 }); // 1KB max
|
|
133
|
+
const key = "large-key";
|
|
134
|
+
const response = {
|
|
135
|
+
body: Buffer.alloc(2 * 1024), // 2KB
|
|
136
|
+
status: 200,
|
|
137
|
+
headers: {},
|
|
138
|
+
model: "gpt-4",
|
|
139
|
+
};
|
|
140
|
+
smallCache.set(key, response);
|
|
141
|
+
expect(smallCache.get(key)).toBeUndefined();
|
|
142
|
+
});
|
|
143
|
+
it("should track hits and misses", () => {
|
|
144
|
+
const key = "stats-key";
|
|
145
|
+
const response = {
|
|
146
|
+
body: Buffer.from("test"),
|
|
147
|
+
status: 200,
|
|
148
|
+
headers: {},
|
|
149
|
+
model: "gpt-4",
|
|
150
|
+
};
|
|
151
|
+
cache.get("missing"); // miss
|
|
152
|
+
cache.set(key, response);
|
|
153
|
+
cache.get(key); // hit
|
|
154
|
+
cache.get(key); // hit
|
|
155
|
+
const stats = cache.getStats();
|
|
156
|
+
expect(stats.misses).toBe(1);
|
|
157
|
+
expect(stats.hits).toBe(2);
|
|
158
|
+
expect(stats.hitRate).toBe("66.7%");
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
describe("TTL expiration", () => {
|
|
162
|
+
it("should expire entries after TTL", () => {
|
|
163
|
+
vi.useFakeTimers();
|
|
164
|
+
const key = "ttl-key";
|
|
165
|
+
const response = {
|
|
166
|
+
body: Buffer.from("test"),
|
|
167
|
+
status: 200,
|
|
168
|
+
headers: {},
|
|
169
|
+
model: "gpt-4",
|
|
170
|
+
};
|
|
171
|
+
cache.set(key, response, 60); // 60 second TTL
|
|
172
|
+
// Should exist before expiration
|
|
173
|
+
expect(cache.get(key)).toBeDefined();
|
|
174
|
+
// Advance time past TTL
|
|
175
|
+
vi.advanceTimersByTime(61 * 1000);
|
|
176
|
+
// Should be expired
|
|
177
|
+
expect(cache.get(key)).toBeUndefined();
|
|
178
|
+
vi.useRealTimers();
|
|
179
|
+
});
|
|
180
|
+
it("should use custom TTL when provided", () => {
|
|
181
|
+
vi.useFakeTimers();
|
|
182
|
+
const key = "custom-ttl";
|
|
183
|
+
const response = {
|
|
184
|
+
body: Buffer.from("test"),
|
|
185
|
+
status: 200,
|
|
186
|
+
headers: {},
|
|
187
|
+
model: "gpt-4",
|
|
188
|
+
};
|
|
189
|
+
cache.set(key, response, 30); // 30 second TTL
|
|
190
|
+
vi.advanceTimersByTime(25 * 1000);
|
|
191
|
+
expect(cache.get(key)).toBeDefined();
|
|
192
|
+
vi.advanceTimersByTime(10 * 1000); // Now 35 seconds total
|
|
193
|
+
expect(cache.get(key)).toBeUndefined();
|
|
194
|
+
vi.useRealTimers();
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
describe("LRU eviction", () => {
|
|
198
|
+
it("should evict oldest entries when at capacity", () => {
|
|
199
|
+
const smallCache = new ResponseCache({ maxSize: 3, defaultTTL: 600 });
|
|
200
|
+
for (let i = 0; i < 5; i++) {
|
|
201
|
+
smallCache.set(`key-${i}`, {
|
|
202
|
+
body: Buffer.from(`response-${i}`),
|
|
203
|
+
status: 200,
|
|
204
|
+
headers: {},
|
|
205
|
+
model: "gpt-4",
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
const stats = smallCache.getStats();
|
|
209
|
+
expect(stats.size).toBeLessThanOrEqual(3);
|
|
210
|
+
expect(stats.evictions).toBeGreaterThan(0);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
describe("clear", () => {
|
|
214
|
+
it("should remove all entries", () => {
|
|
215
|
+
cache.set("key-1", {
|
|
216
|
+
body: Buffer.from("test"),
|
|
217
|
+
status: 200,
|
|
218
|
+
headers: {},
|
|
219
|
+
model: "gpt-4",
|
|
220
|
+
});
|
|
221
|
+
cache.set("key-2", {
|
|
222
|
+
body: Buffer.from("test"),
|
|
223
|
+
status: 200,
|
|
224
|
+
headers: {},
|
|
225
|
+
model: "gpt-4",
|
|
226
|
+
});
|
|
227
|
+
expect(cache.getStats().size).toBe(2);
|
|
228
|
+
cache.clear();
|
|
229
|
+
expect(cache.getStats().size).toBe(0);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
describe("isEnabled", () => {
|
|
233
|
+
it("should return true when enabled", () => {
|
|
234
|
+
expect(cache.isEnabled()).toBe(true);
|
|
235
|
+
});
|
|
236
|
+
it("should return false when disabled", () => {
|
|
237
|
+
const disabledCache = new ResponseCache({ enabled: false });
|
|
238
|
+
expect(disabledCache.isEnabled()).toBe(false);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
//# sourceMappingURL=response-cache.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response-cache.test.js","sourceRoot":"","sources":["../src/response-cache.test.ts"],"names":[],"mappings":"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"}
|
package/dist/retry.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry Logic for ClawRouter
|
|
3
|
+
*
|
|
4
|
+
* Provides fetch wrapper with exponential backoff for transient errors.
|
|
5
|
+
* Retries on 429 (rate limit), 502, 503, 504 (server errors).
|
|
6
|
+
*/
|
|
7
|
+
/** Configuration for retry behavior */
|
|
8
|
+
export type RetryConfig = {
|
|
9
|
+
/** Maximum number of retries (default: 2) */
|
|
10
|
+
maxRetries: number;
|
|
11
|
+
/** Base delay in ms for exponential backoff (default: 500) */
|
|
12
|
+
baseDelayMs: number;
|
|
13
|
+
/** HTTP status codes that trigger a retry (default: [429, 502, 503, 504]) */
|
|
14
|
+
retryableCodes: number[];
|
|
15
|
+
};
|
|
16
|
+
/** Default retry configuration */
|
|
17
|
+
export declare const DEFAULT_RETRY_CONFIG: RetryConfig;
|
|
18
|
+
/**
|
|
19
|
+
* Wrap a fetch-like function with retry logic and exponential backoff.
|
|
20
|
+
*
|
|
21
|
+
* @param fetchFn - The fetch function to wrap (can be standard fetch or x402 payFetch)
|
|
22
|
+
* @param url - URL to fetch
|
|
23
|
+
* @param init - Fetch init options
|
|
24
|
+
* @param config - Retry configuration (optional, uses defaults)
|
|
25
|
+
* @returns Response from successful fetch or last failed attempt
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const response = await fetchWithRetry(
|
|
30
|
+
* fetch,
|
|
31
|
+
* "https://api.example.com/endpoint",
|
|
32
|
+
* { method: "POST", body: JSON.stringify(data) },
|
|
33
|
+
* { maxRetries: 3 }
|
|
34
|
+
* );
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function fetchWithRetry(fetchFn: (url: string, init?: RequestInit) => Promise<Response>, url: string, init?: RequestInit, config?: Partial<RetryConfig>): Promise<Response>;
|
|
38
|
+
/**
|
|
39
|
+
* Check if an error or response indicates a retryable condition.
|
|
40
|
+
*/
|
|
41
|
+
export declare function isRetryable(errorOrResponse: Error | Response, config?: Partial<RetryConfig>): boolean;
|
|
42
|
+
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,uCAAuC;AACvC,MAAM,MAAM,WAAW,GAAG;IACxB,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B,CAAC;AAEF,kCAAkC;AAClC,eAAO,MAAM,oBAAoB,EAAE,WAIlC,CAAC;AAOF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,EAC/D,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,WAAW,EAClB,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC5B,OAAO,CAAC,QAAQ,CAAC,CAsDnB;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,eAAe,EAAE,KAAK,GAAG,QAAQ,EACjC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC5B,OAAO,CAgBT"}
|
package/dist/retry.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry Logic for ClawRouter
|
|
3
|
+
*
|
|
4
|
+
* Provides fetch wrapper with exponential backoff for transient errors.
|
|
5
|
+
* Retries on 429 (rate limit), 502, 503, 504 (server errors).
|
|
6
|
+
*/
|
|
7
|
+
/** Default retry configuration */
|
|
8
|
+
export const DEFAULT_RETRY_CONFIG = {
|
|
9
|
+
maxRetries: 2,
|
|
10
|
+
baseDelayMs: 500,
|
|
11
|
+
retryableCodes: [429, 502, 503, 504],
|
|
12
|
+
};
|
|
13
|
+
/** Sleep for a given number of milliseconds */
|
|
14
|
+
function sleep(ms) {
|
|
15
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Wrap a fetch-like function with retry logic and exponential backoff.
|
|
19
|
+
*
|
|
20
|
+
* @param fetchFn - The fetch function to wrap (can be standard fetch or x402 payFetch)
|
|
21
|
+
* @param url - URL to fetch
|
|
22
|
+
* @param init - Fetch init options
|
|
23
|
+
* @param config - Retry configuration (optional, uses defaults)
|
|
24
|
+
* @returns Response from successful fetch or last failed attempt
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const response = await fetchWithRetry(
|
|
29
|
+
* fetch,
|
|
30
|
+
* "https://api.example.com/endpoint",
|
|
31
|
+
* { method: "POST", body: JSON.stringify(data) },
|
|
32
|
+
* { maxRetries: 3 }
|
|
33
|
+
* );
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export async function fetchWithRetry(fetchFn, url, init, config) {
|
|
37
|
+
const cfg = {
|
|
38
|
+
...DEFAULT_RETRY_CONFIG,
|
|
39
|
+
...config,
|
|
40
|
+
};
|
|
41
|
+
let lastError;
|
|
42
|
+
let lastResponse;
|
|
43
|
+
for (let attempt = 0; attempt <= cfg.maxRetries; attempt++) {
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetchFn(url, init);
|
|
46
|
+
// Success or non-retryable status — return immediately
|
|
47
|
+
if (!cfg.retryableCodes.includes(response.status)) {
|
|
48
|
+
return response;
|
|
49
|
+
}
|
|
50
|
+
// Retryable status — save response and maybe retry
|
|
51
|
+
lastResponse = response;
|
|
52
|
+
// Check for Retry-After header (common with 429)
|
|
53
|
+
const retryAfter = response.headers.get("retry-after");
|
|
54
|
+
let delay;
|
|
55
|
+
if (retryAfter) {
|
|
56
|
+
// Retry-After can be seconds or HTTP-date
|
|
57
|
+
const seconds = parseInt(retryAfter, 10);
|
|
58
|
+
delay = isNaN(seconds) ? cfg.baseDelayMs * Math.pow(2, attempt) : seconds * 1000;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
delay = cfg.baseDelayMs * Math.pow(2, attempt);
|
|
62
|
+
}
|
|
63
|
+
// Only retry if we have attempts left
|
|
64
|
+
if (attempt < cfg.maxRetries) {
|
|
65
|
+
await sleep(delay);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
70
|
+
// Network errors are retryable
|
|
71
|
+
if (attempt < cfg.maxRetries) {
|
|
72
|
+
const delay = cfg.baseDelayMs * Math.pow(2, attempt);
|
|
73
|
+
await sleep(delay);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// All retries exhausted — return last response or throw last error
|
|
78
|
+
if (lastResponse) {
|
|
79
|
+
return lastResponse;
|
|
80
|
+
}
|
|
81
|
+
throw lastError ?? new Error("Max retries exceeded");
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check if an error or response indicates a retryable condition.
|
|
85
|
+
*/
|
|
86
|
+
export function isRetryable(errorOrResponse, config) {
|
|
87
|
+
const retryableCodes = config?.retryableCodes ?? DEFAULT_RETRY_CONFIG.retryableCodes;
|
|
88
|
+
if (errorOrResponse instanceof Response) {
|
|
89
|
+
return retryableCodes.includes(errorOrResponse.status);
|
|
90
|
+
}
|
|
91
|
+
// Network errors are generally retryable
|
|
92
|
+
const message = errorOrResponse.message.toLowerCase();
|
|
93
|
+
return (message.includes("network") ||
|
|
94
|
+
message.includes("timeout") ||
|
|
95
|
+
message.includes("econnreset") ||
|
|
96
|
+
message.includes("econnrefused") ||
|
|
97
|
+
message.includes("socket hang up"));
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=retry.js.map
|