convex-durable-agents 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -7
- package/dist/client/api.d.ts.map +1 -1
- package/dist/client/api.js +23 -4
- package/dist/client/api.js.map +1 -1
- package/dist/client/handler.d.ts +20 -0
- package/dist/client/handler.d.ts.map +1 -1
- package/dist/client/handler.js +239 -117
- package/dist/client/handler.js.map +1 -1
- package/dist/client/streamer.js +1 -1
- package/dist/client/streamer.js.map +1 -1
- package/dist/client/tools.d.ts +17 -0
- package/dist/client/tools.d.ts.map +1 -1
- package/dist/client/tools.js +16 -0
- package/dist/client/tools.js.map +1 -1
- package/dist/client/types.d.ts +75 -1
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/types.js +11 -0
- package/dist/client/types.js.map +1 -1
- package/dist/component/_generated/component.d.ts +89 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/agent.d.ts.map +1 -1
- package/dist/component/agent.js +21 -2
- package/dist/component/agent.js.map +1 -1
- package/dist/component/schema.d.ts +70 -2
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +21 -0
- package/dist/component/schema.js.map +1 -1
- package/dist/component/streams.js +2 -2
- package/dist/component/streams.js.map +1 -1
- package/dist/component/threads.d.ts +92 -2
- package/dist/component/threads.d.ts.map +1 -1
- package/dist/component/threads.js +83 -2
- package/dist/component/threads.js.map +1 -1
- package/dist/component/tool_calls.d.ts +54 -3
- package/dist/component/tool_calls.d.ts.map +1 -1
- package/dist/component/tool_calls.js +345 -35
- package/dist/component/tool_calls.js.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/retry.d.ts +69 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +404 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/streaming.d.ts +4 -0
- package/dist/utils/streaming.d.ts.map +1 -0
- package/dist/utils/streaming.js +4 -0
- package/dist/utils/streaming.js.map +1 -0
- package/package.json +1 -1
- package/src/client/api.ts +24 -4
- package/src/client/handler.ts +308 -133
- package/src/client/streamer.ts +1 -1
- package/src/client/tools.ts +43 -1
- package/src/client/types.ts +60 -0
- package/src/component/_generated/component.ts +103 -0
- package/src/component/agent.ts +24 -2
- package/src/component/schema.ts +22 -0
- package/src/component/streams.ts +2 -2
- package/src/component/threads.ts +92 -3
- package/src/component/tool_calls.ts +421 -44
- package/src/utils/retry.ts +528 -0
- package/src/{streaming.ts → utils/streaming.ts} +2 -3
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js.map +0 -1
- package/dist/streaming.d.ts +0 -3
- package/dist/streaming.d.ts.map +0 -1
- package/dist/streaming.js +0 -4
- package/dist/streaming.js.map +0 -1
- /package/dist/{logger.d.ts → utils/logger.d.ts} +0 -0
- /package/dist/{logger.js → utils/logger.js} +0 -0
- /package/src/{logger.ts → utils/logger.ts} +0 -0
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import type { ActionCtx, RetryBackoffConfig } from "../client/types.js";
|
|
2
|
+
|
|
3
|
+
export type RetryErrorKind =
|
|
4
|
+
| "network"
|
|
5
|
+
| "rate_limited"
|
|
6
|
+
| "provider_5xx"
|
|
7
|
+
| "context_window_exceeded"
|
|
8
|
+
| "insufficient_credits"
|
|
9
|
+
| "invalid_request"
|
|
10
|
+
| "auth"
|
|
11
|
+
| "unknown";
|
|
12
|
+
|
|
13
|
+
export type RetryErrorSignal = {
|
|
14
|
+
name?: string;
|
|
15
|
+
code?: string;
|
|
16
|
+
status?: number;
|
|
17
|
+
statusCode?: number;
|
|
18
|
+
isRetryable?: boolean;
|
|
19
|
+
retryErrorReason?: "maxRetriesExceeded" | "errorNotRetryable" | "abort";
|
|
20
|
+
responseHeaders?: Record<string, string>;
|
|
21
|
+
responseBody?: string;
|
|
22
|
+
providerCode?: string;
|
|
23
|
+
providerType?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type RetryErrorClassification = {
|
|
27
|
+
kind: RetryErrorKind;
|
|
28
|
+
retryable: boolean;
|
|
29
|
+
requiresExplicitHandling: boolean;
|
|
30
|
+
signal: RetryErrorSignal;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type RetryDecision =
|
|
34
|
+
| { action: "retry"; delayMs?: number; reason?: string }
|
|
35
|
+
| {
|
|
36
|
+
action: "fail";
|
|
37
|
+
kind?: RetryErrorKind;
|
|
38
|
+
reason?: string;
|
|
39
|
+
requiresExplicitHandling?: boolean;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type RetryContext = {
|
|
43
|
+
threadId: string;
|
|
44
|
+
streamId: string;
|
|
45
|
+
attempt: number;
|
|
46
|
+
maxAttempts: number;
|
|
47
|
+
toolCallsScheduled: number;
|
|
48
|
+
streamPartCount: number;
|
|
49
|
+
error: unknown;
|
|
50
|
+
normalizedError: string;
|
|
51
|
+
defaultClassification: RetryErrorClassification;
|
|
52
|
+
defaultDecision: RetryDecision;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type RetryOptions = {
|
|
56
|
+
enabled?: boolean;
|
|
57
|
+
maxAttempts?: number;
|
|
58
|
+
backoff?: RetryBackoffConfig;
|
|
59
|
+
retryAfterToolCalls?: boolean;
|
|
60
|
+
classify?: (ctx: ActionCtx, input: RetryContext) => RetryDecision | Promise<RetryDecision>;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type ToolErrorInfo = {
|
|
64
|
+
message: string;
|
|
65
|
+
name?: string;
|
|
66
|
+
code?: string;
|
|
67
|
+
status?: number;
|
|
68
|
+
statusCode?: number;
|
|
69
|
+
causeMessage?: string;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
type RecordLike = Record<string, unknown>;
|
|
73
|
+
|
|
74
|
+
export const DEFAULT_RETRY_MAX_ATTEMPTS = 3;
|
|
75
|
+
|
|
76
|
+
export const DEFAULT_RETRY_BACKOFF: RetryBackoffConfig = {
|
|
77
|
+
strategy: "exponential",
|
|
78
|
+
initialDelayMs: 250,
|
|
79
|
+
multiplier: 2,
|
|
80
|
+
maxDelayMs: 4000,
|
|
81
|
+
jitter: true,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const CONTEXT_OVERFLOW_PATTERNS = [
|
|
85
|
+
/prompt is too long/i,
|
|
86
|
+
/input is too long for requested model/i,
|
|
87
|
+
/exceeds the context window/i,
|
|
88
|
+
/input token count.*exceeds the maximum/i,
|
|
89
|
+
/maximum prompt length is \d+/i,
|
|
90
|
+
/reduce the length of the messages/i,
|
|
91
|
+
/maximum context length is \d+ tokens/i,
|
|
92
|
+
/exceeds the limit of \d+/i,
|
|
93
|
+
/exceeds the available context size/i,
|
|
94
|
+
/greater than the context length/i,
|
|
95
|
+
/context window exceeds limit/i,
|
|
96
|
+
/exceeded model token limit/i,
|
|
97
|
+
/context[_ ]length[_ ]exceeded/i,
|
|
98
|
+
/^4(00|13)\s*(status code)?\s*\(no body\)/i,
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
const INSUFFICIENT_CREDITS_PATTERNS = [
|
|
102
|
+
/insufficient[_ ]quota/i,
|
|
103
|
+
/out of credits/i,
|
|
104
|
+
/quota exceeded/i,
|
|
105
|
+
/credit balance/i,
|
|
106
|
+
/freeusagelimiterror/i,
|
|
107
|
+
/usage limit reached/i,
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
const AUTH_PATTERNS = [/unauthorized/i, /forbidden/i, /invalid api key/i, /authentication/i];
|
|
111
|
+
|
|
112
|
+
const RATE_LIMIT_PATTERNS = [/too many requests/i, /rate.?limit/i, /resource.?exhausted/i];
|
|
113
|
+
|
|
114
|
+
const INVALID_REQUEST_PATTERNS = [/invalid request/i, /invalid argument/i, /validation error/i, /invalid prompt/i];
|
|
115
|
+
|
|
116
|
+
const NETWORK_PATTERNS = [
|
|
117
|
+
/network/i,
|
|
118
|
+
/econnreset/i,
|
|
119
|
+
/econnrefused/i,
|
|
120
|
+
/etimedout/i,
|
|
121
|
+
/ehostunreach/i,
|
|
122
|
+
/connection reset/i,
|
|
123
|
+
/connection refused/i,
|
|
124
|
+
/fetch failed/i,
|
|
125
|
+
/socket hang up/i,
|
|
126
|
+
/upstream.?connect/i,
|
|
127
|
+
/other.?side.?closed/i,
|
|
128
|
+
/cannot connect to api/i,
|
|
129
|
+
/(?:connection|stream|socket)\s+terminated/i,
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
const NETWORK_CODES = new Set([
|
|
133
|
+
"ECONNRESET",
|
|
134
|
+
"ECONNREFUSED",
|
|
135
|
+
"ETIMEDOUT",
|
|
136
|
+
"EHOSTUNREACH",
|
|
137
|
+
"EPIPE",
|
|
138
|
+
"ENOTFOUND",
|
|
139
|
+
"CONNECTIONREFUSED",
|
|
140
|
+
"CONNECTIONCLOSED",
|
|
141
|
+
"FAILEDTOOPENSOCKET",
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
const CONTEXT_OVERFLOW_CODES = new Set([
|
|
145
|
+
"context_length_exceeded",
|
|
146
|
+
"context_window_exceeded",
|
|
147
|
+
"prompt_too_long",
|
|
148
|
+
"token_limit_exceeded",
|
|
149
|
+
]);
|
|
150
|
+
|
|
151
|
+
const INSUFFICIENT_CREDITS_CODES = new Set([
|
|
152
|
+
"insufficient_quota",
|
|
153
|
+
"usage_limit_reached",
|
|
154
|
+
"usage_not_included",
|
|
155
|
+
"credit_balance_too_low",
|
|
156
|
+
"quota_exceeded",
|
|
157
|
+
]);
|
|
158
|
+
|
|
159
|
+
const RATE_LIMIT_CODES = new Set(["rate_limit_exceeded", "too_many_requests", "resource_exhausted"]);
|
|
160
|
+
|
|
161
|
+
const AUTH_CODES = new Set(["invalid_api_key", "authentication_error", "unauthorized", "forbidden"]);
|
|
162
|
+
|
|
163
|
+
const INVALID_REQUEST_CODES = new Set(["invalid_request_error", "invalid_prompt", "bad_request"]);
|
|
164
|
+
|
|
165
|
+
function asRecord(value: unknown): RecordLike | undefined {
|
|
166
|
+
if (typeof value !== "object" || value === null) {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
return value as RecordLike;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function asString(value: unknown): string | undefined {
|
|
173
|
+
return typeof value === "string" ? value : undefined;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function asNumber(value: unknown): number | undefined {
|
|
177
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function parseStatusCodeFromMessage(message: string): number | undefined {
|
|
181
|
+
const patterns = [/status(?: code)?[:=\s]+(\d{3})/i, /\b(\d{3})\s*(?:status code)/i, /^\s*(\d{3})\b/];
|
|
182
|
+
for (const pattern of patterns) {
|
|
183
|
+
const match = message.match(pattern);
|
|
184
|
+
if (!match?.[1]) continue;
|
|
185
|
+
const status = Number.parseInt(match[1], 10);
|
|
186
|
+
if (!Number.isNaN(status)) return status;
|
|
187
|
+
}
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function parseJsonRecord(value: unknown): RecordLike | undefined {
|
|
192
|
+
if (typeof value === "string") {
|
|
193
|
+
try {
|
|
194
|
+
const parsed = JSON.parse(value) as unknown;
|
|
195
|
+
return asRecord(parsed);
|
|
196
|
+
} catch {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return asRecord(value);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function normalizeHeaderRecord(value: unknown): Record<string, string> | undefined {
|
|
204
|
+
const record = asRecord(value);
|
|
205
|
+
if (!record) {
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
const normalized: Record<string, string> = {};
|
|
209
|
+
for (const [key, headerValue] of Object.entries(record)) {
|
|
210
|
+
if (typeof headerValue === "string") {
|
|
211
|
+
normalized[key.toLowerCase()] = headerValue;
|
|
212
|
+
} else if (typeof headerValue === "number" || typeof headerValue === "boolean") {
|
|
213
|
+
normalized[key.toLowerCase()] = String(headerValue);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function parseRetryErrorReason(value: unknown): RetryErrorSignal["retryErrorReason"] | undefined {
|
|
220
|
+
if (value === "maxRetriesExceeded" || value === "errorNotRetryable" || value === "abort") {
|
|
221
|
+
return value;
|
|
222
|
+
}
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function getNestedErrorFields(payload: RecordLike): { code?: string; type?: string } {
|
|
227
|
+
const nestedError = asRecord(payload.error);
|
|
228
|
+
const code = asString(nestedError?.code) ?? asString(payload.code);
|
|
229
|
+
const type = asString(nestedError?.type) ?? asString(payload.type);
|
|
230
|
+
return { code, type };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function includesAnyPattern(message: string, patterns: RegExp[]): boolean {
|
|
234
|
+
return patterns.some((pattern) => pattern.test(message));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function clampDelayMs(delayMs: number): number {
|
|
238
|
+
if (!Number.isFinite(delayMs) || delayMs < 0) {
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
return Math.floor(delayMs);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function computeBackoffDelayMs(attempt: number, backoff: RetryBackoffConfig | undefined): number {
|
|
245
|
+
const policy = backoff ?? DEFAULT_RETRY_BACKOFF;
|
|
246
|
+
if ("delayMs" in policy) {
|
|
247
|
+
const delayMs = clampDelayMs(policy.delayMs);
|
|
248
|
+
if (!policy.jitter) return delayMs;
|
|
249
|
+
return Math.floor(Math.random() * (delayMs + 1));
|
|
250
|
+
}
|
|
251
|
+
const initialDelayMs = clampDelayMs(policy.initialDelayMs);
|
|
252
|
+
const multiplier = Number.isFinite(policy.multiplier ?? 2) ? (policy.multiplier ?? 2) : 2;
|
|
253
|
+
const unbounded = initialDelayMs * multiplier ** Math.max(0, attempt - 1);
|
|
254
|
+
const maxDelayMs = policy.maxDelayMs == null ? unbounded : clampDelayMs(policy.maxDelayMs);
|
|
255
|
+
const delayMs = Math.min(unbounded, maxDelayMs);
|
|
256
|
+
if (!policy.jitter) return delayMs;
|
|
257
|
+
return Math.floor(Math.random() * (delayMs + 1));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function normalizeErrorMessage(error: unknown): string {
|
|
261
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
262
|
+
return errorMessage || "Unknown error";
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function extractRetryErrorSignal(error: unknown): RetryErrorSignal {
|
|
266
|
+
const signal: RetryErrorSignal = {};
|
|
267
|
+
const queue: unknown[] = [error];
|
|
268
|
+
const seen = new Set<object>();
|
|
269
|
+
let inspected = 0;
|
|
270
|
+
|
|
271
|
+
while (queue.length > 0 && inspected < 16) {
|
|
272
|
+
const current = queue.shift();
|
|
273
|
+
inspected += 1;
|
|
274
|
+
if (!current) continue;
|
|
275
|
+
|
|
276
|
+
if (typeof current === "object") {
|
|
277
|
+
if (seen.has(current)) continue;
|
|
278
|
+
seen.add(current);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const record = asRecord(current);
|
|
282
|
+
if (!record) continue;
|
|
283
|
+
|
|
284
|
+
signal.name ??= asString(record.name);
|
|
285
|
+
signal.code ??= asString(record.code);
|
|
286
|
+
signal.status ??= asNumber(record.status);
|
|
287
|
+
signal.statusCode ??= asNumber(record.statusCode);
|
|
288
|
+
signal.responseBody ??= asString(record.responseBody);
|
|
289
|
+
signal.responseHeaders ??= normalizeHeaderRecord(record.responseHeaders);
|
|
290
|
+
signal.retryErrorReason ??= parseRetryErrorReason(record.reason);
|
|
291
|
+
|
|
292
|
+
if (typeof record.isRetryable === "boolean" && signal.isRetryable == null) {
|
|
293
|
+
signal.isRetryable = record.isRetryable;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const dataPayload = parseJsonRecord(record.data);
|
|
297
|
+
if (dataPayload) {
|
|
298
|
+
const nested = getNestedErrorFields(dataPayload);
|
|
299
|
+
signal.providerCode ??= nested.code;
|
|
300
|
+
signal.providerType ??= nested.type;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const bodyPayload = parseJsonRecord(signal.responseBody);
|
|
304
|
+
if (bodyPayload) {
|
|
305
|
+
const nested = getNestedErrorFields(bodyPayload);
|
|
306
|
+
signal.providerCode ??= nested.code;
|
|
307
|
+
signal.providerType ??= nested.type;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if ("cause" in record) queue.push(record.cause);
|
|
311
|
+
if ("lastError" in record) queue.push(record.lastError);
|
|
312
|
+
if (Array.isArray(record.errors)) {
|
|
313
|
+
queue.push(...record.errors.slice(0, 4));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (signal.providerCode) signal.providerCode = signal.providerCode.toLowerCase();
|
|
318
|
+
if (signal.providerType) signal.providerType = signal.providerType.toLowerCase();
|
|
319
|
+
if (signal.code) signal.code = signal.code.toUpperCase();
|
|
320
|
+
if (signal.name) signal.name = signal.name.toLowerCase();
|
|
321
|
+
|
|
322
|
+
return signal;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function extractRetryAfterDelayMs(signal: RetryErrorSignal): number | undefined {
|
|
326
|
+
const headers = signal.responseHeaders;
|
|
327
|
+
if (!headers) return undefined;
|
|
328
|
+
|
|
329
|
+
const retryAfterMs = headers["retry-after-ms"];
|
|
330
|
+
if (retryAfterMs) {
|
|
331
|
+
const parsedMs = Number.parseFloat(retryAfterMs);
|
|
332
|
+
if (Number.isFinite(parsedMs) && parsedMs >= 0 && parsedMs <= 60_000) {
|
|
333
|
+
return Math.ceil(parsedMs);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const retryAfter = headers["retry-after"];
|
|
338
|
+
if (retryAfter) {
|
|
339
|
+
const parsedSeconds = Number.parseFloat(retryAfter);
|
|
340
|
+
if (Number.isFinite(parsedSeconds)) {
|
|
341
|
+
const delayMs = parsedSeconds * 1000;
|
|
342
|
+
if (delayMs >= 0 && delayMs <= 60_000) {
|
|
343
|
+
return Math.ceil(delayMs);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const retryAfterDate = Date.parse(retryAfter);
|
|
347
|
+
if (!Number.isNaN(retryAfterDate)) {
|
|
348
|
+
const delayMs = retryAfterDate - Date.now();
|
|
349
|
+
if (delayMs >= 0 && delayMs <= 60_000) {
|
|
350
|
+
return Math.ceil(delayMs);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export function classifyRetryErrorDefault(error: unknown): RetryErrorClassification {
|
|
359
|
+
const signal = extractRetryErrorSignal(error);
|
|
360
|
+
const message = normalizeErrorMessage(error);
|
|
361
|
+
const msg = message.toLowerCase();
|
|
362
|
+
const status = signal.statusCode ?? signal.status ?? parseStatusCodeFromMessage(msg);
|
|
363
|
+
const providerCode = signal.providerCode;
|
|
364
|
+
|
|
365
|
+
const isAbortError =
|
|
366
|
+
signal.retryErrorReason === "abort" ||
|
|
367
|
+
signal.name === "aborterror" ||
|
|
368
|
+
signal.name === "responseaborted" ||
|
|
369
|
+
signal.name === "timeouterror" ||
|
|
370
|
+
msg.includes("request was aborted");
|
|
371
|
+
|
|
372
|
+
if (isAbortError) {
|
|
373
|
+
return { kind: "unknown", retryable: false, requiresExplicitHandling: false, signal };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (providerCode && CONTEXT_OVERFLOW_CODES.has(providerCode)) {
|
|
377
|
+
return { kind: "context_window_exceeded", retryable: false, requiresExplicitHandling: true, signal };
|
|
378
|
+
}
|
|
379
|
+
if (includesAnyPattern(msg, CONTEXT_OVERFLOW_PATTERNS)) {
|
|
380
|
+
return { kind: "context_window_exceeded", retryable: false, requiresExplicitHandling: true, signal };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (providerCode && INSUFFICIENT_CREDITS_CODES.has(providerCode)) {
|
|
384
|
+
return { kind: "insufficient_credits", retryable: false, requiresExplicitHandling: true, signal };
|
|
385
|
+
}
|
|
386
|
+
if (includesAnyPattern(msg, INSUFFICIENT_CREDITS_PATTERNS)) {
|
|
387
|
+
return { kind: "insufficient_credits", retryable: false, requiresExplicitHandling: true, signal };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (
|
|
391
|
+
status === 401 ||
|
|
392
|
+
status === 403 ||
|
|
393
|
+
(providerCode && AUTH_CODES.has(providerCode)) ||
|
|
394
|
+
includesAnyPattern(msg, AUTH_PATTERNS)
|
|
395
|
+
) {
|
|
396
|
+
return { kind: "auth", retryable: false, requiresExplicitHandling: true, signal };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (
|
|
400
|
+
status === 429 ||
|
|
401
|
+
(providerCode && RATE_LIMIT_CODES.has(providerCode)) ||
|
|
402
|
+
includesAnyPattern(msg, RATE_LIMIT_PATTERNS)
|
|
403
|
+
) {
|
|
404
|
+
return { kind: "rate_limited", retryable: true, requiresExplicitHandling: false, signal };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (status != null && status >= 500 && status <= 599) {
|
|
408
|
+
return { kind: "provider_5xx", retryable: true, requiresExplicitHandling: false, signal };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (status === 408 || status === 409) {
|
|
412
|
+
return { kind: "network", retryable: true, requiresExplicitHandling: false, signal };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (
|
|
416
|
+
(signal.code && NETWORK_CODES.has(signal.code)) ||
|
|
417
|
+
includesAnyPattern(msg, NETWORK_PATTERNS) ||
|
|
418
|
+
(signal.isRetryable === true && status == null)
|
|
419
|
+
) {
|
|
420
|
+
return { kind: "network", retryable: true, requiresExplicitHandling: false, signal };
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (
|
|
424
|
+
status === 400 ||
|
|
425
|
+
status === 422 ||
|
|
426
|
+
(providerCode && INVALID_REQUEST_CODES.has(providerCode)) ||
|
|
427
|
+
includesAnyPattern(msg, INVALID_REQUEST_PATTERNS)
|
|
428
|
+
) {
|
|
429
|
+
return { kind: "invalid_request", retryable: false, requiresExplicitHandling: true, signal };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return { kind: "unknown", retryable: false, requiresExplicitHandling: false, signal };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function extractToolErrorInfo(error: unknown): ToolErrorInfo {
|
|
436
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
437
|
+
const info: ToolErrorInfo = {
|
|
438
|
+
message: message || "Unknown error",
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
const root = asRecord(error);
|
|
442
|
+
if (root) {
|
|
443
|
+
info.name = asString(root.name);
|
|
444
|
+
info.code = asString(root.code) ?? asString(root.errno);
|
|
445
|
+
info.status = asNumber(root.status);
|
|
446
|
+
info.statusCode = asNumber(root.statusCode);
|
|
447
|
+
const cause = asRecord(root.cause);
|
|
448
|
+
if (cause) {
|
|
449
|
+
info.causeMessage = asString(cause.message);
|
|
450
|
+
if (!info.code) info.code = asString(cause.code) ?? asString(cause.errno);
|
|
451
|
+
if (info.status == null) info.status = asNumber(cause.status);
|
|
452
|
+
if (info.statusCode == null) info.statusCode = asNumber(cause.statusCode);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (info.status == null && info.statusCode == null) {
|
|
457
|
+
const parsedStatus = parseStatusCodeFromMessage(info.message);
|
|
458
|
+
if (parsedStatus != null) {
|
|
459
|
+
info.statusCode = parsedStatus;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (info.code) info.code = info.code.toUpperCase();
|
|
464
|
+
return info;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export function isRetryableToolErrorDefault(error: ToolErrorInfo | string): boolean {
|
|
468
|
+
const info = typeof error === "string" ? ({ message: error } satisfies ToolErrorInfo) : error;
|
|
469
|
+
const msg = info.message.toLowerCase();
|
|
470
|
+
const status = info.statusCode ?? info.status ?? parseStatusCodeFromMessage(msg);
|
|
471
|
+
const code = info.code?.toUpperCase();
|
|
472
|
+
const hasExplicit5xxStatus = /\bstatus(?:\s+code)?\s*[:=]?\s*5\d{2}\b/.test(msg);
|
|
473
|
+
|
|
474
|
+
if (status === 429 || status === 408 || status === 409 || (status != null && status >= 500 && status <= 599)) {
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
if (status === 401 || status === 403 || (status != null && status >= 400 && status <= 499)) {
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (
|
|
482
|
+
code === "ECONNRESET" ||
|
|
483
|
+
code === "ECONNREFUSED" ||
|
|
484
|
+
code === "ETIMEDOUT" ||
|
|
485
|
+
code === "EHOSTUNREACH" ||
|
|
486
|
+
code === "EPIPE" ||
|
|
487
|
+
code === "ENOTFOUND" ||
|
|
488
|
+
code === "CONNECTIONREFUSED" ||
|
|
489
|
+
code === "CONNECTIONCLOSED" ||
|
|
490
|
+
code === "FAILEDTOOPENSOCKET"
|
|
491
|
+
) {
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
if (
|
|
495
|
+
msg.includes("rate limit") ||
|
|
496
|
+
msg.includes("too many requests") ||
|
|
497
|
+
msg.includes("status: 429") ||
|
|
498
|
+
hasExplicit5xxStatus ||
|
|
499
|
+
msg.includes("internal server error") ||
|
|
500
|
+
msg.includes("bad gateway")
|
|
501
|
+
) {
|
|
502
|
+
return true;
|
|
503
|
+
}
|
|
504
|
+
if (
|
|
505
|
+
msg.includes("network") ||
|
|
506
|
+
msg.includes("econnreset") ||
|
|
507
|
+
msg.includes("econnrefused") ||
|
|
508
|
+
msg.includes("etimedout") ||
|
|
509
|
+
msg.includes("ehostunreach") ||
|
|
510
|
+
msg.includes("connection reset") ||
|
|
511
|
+
msg.includes("connection refused") ||
|
|
512
|
+
msg.includes("fetch failed") ||
|
|
513
|
+
msg.includes("socket hang up") ||
|
|
514
|
+
msg.includes("upstream connect") ||
|
|
515
|
+
msg.includes("other side closed") ||
|
|
516
|
+
msg.includes("cannot connect to api")
|
|
517
|
+
) {
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export function isRetryableDecision(value: unknown): boolean {
|
|
524
|
+
if (value === true) return true;
|
|
525
|
+
if (typeof value !== "object" || value === null) return false;
|
|
526
|
+
if (!("retryable" in value)) return false;
|
|
527
|
+
return (value as { retryable?: unknown }).retryable === true;
|
|
528
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export const STREAM_HEARTBEAT_INTERVAL_MS =
|
|
2
|
-
const STREAM_HEARTBEAT_JITTER_MS =
|
|
3
|
-
|
|
1
|
+
export const STREAM_HEARTBEAT_INTERVAL_MS = 30_000;
|
|
2
|
+
export const STREAM_HEARTBEAT_JITTER_MS = 10_000;
|
|
4
3
|
export const STREAM_LIVENESS_THRESHOLD_MS = STREAM_HEARTBEAT_INTERVAL_MS + STREAM_HEARTBEAT_JITTER_MS;
|
package/dist/logger.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAEA,qBAAa,MAAM;aACW,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM;IAExC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;IAM5C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;IAI3C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;IAI3C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;CAG7C"}
|
package/dist/logger.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,MAAM,aAAa,GAAG,KAAK,CAAC;AAE5B,MAAM,OAAO,MAAM;IACW;IAA5B,YAA4B,IAAY;QAAZ,SAAI,GAAJ,IAAI,CAAQ;IAAG,CAAC;IAE5C,KAAK,CAAC,OAAe,EAAE,GAAG,IAAW;QACnC,IAAI,aAAa,EAAE,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAG,IAAW;QAClC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC;IACrD,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAG,IAAW;QAClC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC;IACrD,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAG,IAAW;QACnC,OAAO,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC;IACtD,CAAC;CACF"}
|
package/dist/streaming.d.ts
DELETED
package/dist/streaming.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"streaming.d.ts","sourceRoot":"","sources":["../src/streaming.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAGnD,eAAO,MAAM,4BAA4B,QAA4D,CAAC"}
|
package/dist/streaming.js
DELETED
package/dist/streaming.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"streaming.js","sourceRoot":"","sources":["../src/streaming.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,4BAA4B,GAAG,MAAM,CAAC;AACnD,MAAM,0BAA0B,GAAG,MAAM,CAAC;AAE1C,MAAM,CAAC,MAAM,4BAA4B,GAAG,4BAA4B,GAAG,0BAA0B,CAAC"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|