@kolbo/mcp 1.12.0 → 1.12.1
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/package.json +1 -1
- package/src/polling.js +43 -1
package/package.json
CHANGED
package/src/polling.js
CHANGED
|
@@ -25,6 +25,25 @@ class GenerationFailedError extends Error {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// HTTP status codes / fetch failure modes we treat as transient when
|
|
29
|
+
// polling. The job is almost always still running on the server (or about
|
|
30
|
+
// to come back up) — bailing out makes the agent give up on a generation
|
|
31
|
+
// that completes 2 seconds later.
|
|
32
|
+
const TRANSIENT_STATUS_CODES = new Set([0, 408, 425, 429, 500, 502, 503, 504, 522, 524]);
|
|
33
|
+
|
|
34
|
+
function isTransientPollError(err) {
|
|
35
|
+
if (!err) return false;
|
|
36
|
+
// fetch() rejects with a TypeError on network failure (ECONNREFUSED,
|
|
37
|
+
// ECONNRESET, DNS failure, kolbo-api restart, etc.) — no `status` on
|
|
38
|
+
// the error object.
|
|
39
|
+
if (err.name === 'TypeError') return true;
|
|
40
|
+
if (err.code === 'ECONNRESET' || err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT' || err.code === 'EPIPE') return true;
|
|
41
|
+
// KolboApiError tags status on the options object.
|
|
42
|
+
const status = err.status ?? err.options?.status;
|
|
43
|
+
if (typeof status === 'number' && TRANSIENT_STATUS_CODES.has(status)) return true;
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
28
47
|
async function pollUntilDone(client, generationId, options = {}) {
|
|
29
48
|
const {
|
|
30
49
|
interval = 5000,
|
|
@@ -34,13 +53,36 @@ async function pollUntilDone(client, generationId, options = {}) {
|
|
|
34
53
|
|
|
35
54
|
const startTime = Date.now();
|
|
36
55
|
const url = statusUrl || `/v1/generate/${encodeURIComponent(generationId)}/status`;
|
|
56
|
+
let transientFailures = 0;
|
|
37
57
|
|
|
38
58
|
while (true) {
|
|
39
59
|
if (Date.now() - startTime > timeout) {
|
|
40
60
|
throw new PollingTimeoutError(generationId, timeout);
|
|
41
61
|
}
|
|
42
62
|
|
|
43
|
-
|
|
63
|
+
let result;
|
|
64
|
+
try {
|
|
65
|
+
result = await client.get(url);
|
|
66
|
+
transientFailures = 0; // reset on successful poll
|
|
67
|
+
} catch (err) {
|
|
68
|
+
// kolbo-api restart, transient network blip, rate-limit, 5xx —
|
|
69
|
+
// the job is still alive on the server (or coming back). Don't
|
|
70
|
+
// abandon the polling loop just because one status check failed:
|
|
71
|
+
// wait a bit longer and try again. After ~30 consecutive failures
|
|
72
|
+
// (~2.5 minutes at the 5s base interval) we still surface the
|
|
73
|
+
// last error so a truly dead backend doesn't loop forever.
|
|
74
|
+
if (isTransientPollError(err)) {
|
|
75
|
+
transientFailures++;
|
|
76
|
+
if (transientFailures > 30) {
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
const backoff = Math.min(interval * Math.pow(1.5, Math.min(transientFailures - 1, 5)), 30000);
|
|
80
|
+
await new Promise((resolve) => setTimeout(resolve, backoff));
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// Non-transient (auth, 4xx other than 408/425/429) — bubble up.
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
44
86
|
|
|
45
87
|
if (result.state === 'completed') {
|
|
46
88
|
return result;
|