@genesislcap/foundation-ai 15.8.2 → 15.8.3
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-with-retry.d.ts","sourceRoot":"","sources":["../../../src/transports/post-with-retry.ts"],"names":[],"mappings":"AAYA;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC;;;OAGG;IACH,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE,kBAAkB,CAAC;IAChC,uFAAuF;IACvF,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,4FAA4F;IAC5F,OAAO,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,YAAY,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;IACtB,uEAAuE;IACvE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAID;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"post-with-retry.d.ts","sourceRoot":"","sources":["../../../src/transports/post-with-retry.ts"],"names":[],"mappings":"AAYA;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC;;;OAGG;IACH,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE,kBAAkB,CAAC;IAChC,uFAAuF;IACvF,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,4FAA4F;IAC5F,OAAO,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,YAAY,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;IACtB,uEAAuE;IACvE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAID;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC,CAuMnF"}
|
|
@@ -16,7 +16,7 @@ const MAX_RETRIES = 5;
|
|
|
16
16
|
*/
|
|
17
17
|
export function postWithRetry(options) {
|
|
18
18
|
return __awaiter(this, void 0, void 0, function* () {
|
|
19
|
-
var _a, _b, _c, _d;
|
|
19
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
20
20
|
const { url, headers, body, credentials, vendorLabel, retryableStatuses, timeout, stallTimeout, backoffBaseMs, signal, } = options;
|
|
21
21
|
// Exponential backoff between retries: base, 2×, 4×, 8×, 16×. Abortable on the
|
|
22
22
|
// CALLER signal only — the timeout is cleared before each backoff, so a caller
|
|
@@ -26,10 +26,29 @@ export function postWithRetry(options) {
|
|
|
26
26
|
/* oxlint-disable no-await-in-loop -- intentional sequential retries with backoff */
|
|
27
27
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt += 1) {
|
|
28
28
|
const timeoutController = new AbortController();
|
|
29
|
+
// Which phase the request reached before the clock fired. A bare "timed out"
|
|
30
|
+
// cannot distinguish "the service never answered at all" from "it answered,
|
|
31
|
+
// then produced no body" — and those have completely different owners
|
|
32
|
+
// (network/gateway vs the service itself). `fetch` resolves on HEADERS, so
|
|
33
|
+
// the phase is knowable here and is the single most useful fact when the
|
|
34
|
+
// only evidence available is a client-side log.
|
|
35
|
+
// `performance.now()` (monotonic), not `Date.now()`: an NTP step or suspend/resume mid-request
|
|
36
|
+
// would otherwise land a nonsense duration in the very message someone is reading to diagnose
|
|
37
|
+
// a hang. Only differences are used, so the arbitrary time origin is irrelevant.
|
|
38
|
+
const startedAt = performance.now();
|
|
39
|
+
let headersAt;
|
|
40
|
+
let responseCtype;
|
|
41
|
+
const timedOutMessage = () => {
|
|
42
|
+
const waited = Math.round(performance.now() - startedAt);
|
|
43
|
+
if (headersAt === undefined) {
|
|
44
|
+
return `${vendorLabel} request timed out after ${waited}ms — no response headers (the service never answered; nothing was received to stall on)`;
|
|
45
|
+
}
|
|
46
|
+
return `${vendorLabel} request timed out after ${waited}ms — headers arrived in ${Math.round(headersAt - startedAt)}ms (content-type: ${responseCtype !== null && responseCtype !== void 0 ? responseCtype : 'unknown'}) but no body followed`;
|
|
47
|
+
};
|
|
29
48
|
// Wall clock for connect/headers (and the whole body on the legacy JSON
|
|
30
49
|
// path). The framed path swaps it for the inactivity timer below once the
|
|
31
50
|
// heartbeat body starts streaming.
|
|
32
|
-
let timeoutId = setTimeout(() => timeoutController.abort(new DOMException(
|
|
51
|
+
let timeoutId = setTimeout(() => timeoutController.abort(new DOMException(timedOutMessage(), 'TimeoutError')), timeout);
|
|
33
52
|
// Re-armed on every received chunk. With proxy pings every 5s it only fires
|
|
34
53
|
// when the proxy/network actually dies — a framed body that keeps streaming
|
|
35
54
|
// has no wall cap (max_tokens bounds generation; the proxy watches upstream).
|
|
@@ -57,6 +76,12 @@ export function postWithRetry(options) {
|
|
|
57
76
|
signal: combinedSignal,
|
|
58
77
|
credentials,
|
|
59
78
|
});
|
|
79
|
+
// Headers are in — record the phase so a later timeout can say so. Cheap and
|
|
80
|
+
// unconditional: the value is only read when the clock actually fires.
|
|
81
|
+
headersAt = performance.now();
|
|
82
|
+
// Defensive: `headers` is optional on the Response-likes callers and tests supply, and a
|
|
83
|
+
// diagnostic must never be the thing that breaks the request it is describing.
|
|
84
|
+
responseCtype = (_c = (_b = (_a = response.headers) === null || _a === void 0 ? void 0 : _a.get) === null || _b === void 0 ? void 0 : _b.call(_a, 'content-type')) !== null && _c !== void 0 ? _c : undefined;
|
|
60
85
|
if (!response.ok) {
|
|
61
86
|
// One read of the error body per failing attempt — it feeds BOTH the
|
|
62
87
|
// budget classification and the thrown message, and the response is
|
|
@@ -99,7 +124,7 @@ export function postWithRetry(options) {
|
|
|
99
124
|
// The proxy streams NDJSON heartbeat frames when it supports them; an old
|
|
100
125
|
// proxy ignores our Accept header and returns plain JSON, so branch on
|
|
101
126
|
// what actually came back.
|
|
102
|
-
if (((
|
|
127
|
+
if (((_e = (_d = response.headers) === null || _d === void 0 ? void 0 : _d.get('content-type')) === null || _e === void 0 ? void 0 : _e.toLowerCase().includes('application/x-ndjson')) &&
|
|
103
128
|
response.body) {
|
|
104
129
|
rearmStallTimer();
|
|
105
130
|
const frame = yield readFramedBody(response.body, rearmStallTimer, vendorLabel, stallTimeout);
|
|
@@ -111,7 +136,7 @@ export function postWithRetry(options) {
|
|
|
111
136
|
if (frame.code === 'UPSTREAM_STALLED') {
|
|
112
137
|
// The proxy watchdog saw no upstream data — same tagged shape as a
|
|
113
138
|
// local stall, so the driver's TimeoutError branch handles both.
|
|
114
|
-
throw new DOMException(`${vendorLabel} request stalled: ${(
|
|
139
|
+
throw new DOMException(`${vendorLabel} request stalled: ${(_f = frame.error) !== null && _f !== void 0 ? _f : 'no upstream data'}`, 'TimeoutError');
|
|
115
140
|
}
|
|
116
141
|
// The budget wall, framed (GENC-1464) — the in-band twin of the 402 above,
|
|
117
142
|
// through the same predicate, keyed on status OR code. Classified before
|
|
@@ -130,7 +155,7 @@ export function postWithRetry(options) {
|
|
|
130
155
|
yield backoff(attempt);
|
|
131
156
|
continue;
|
|
132
157
|
}
|
|
133
|
-
throw new Error(`${vendorLabel} request error ${frame.status}: ${(
|
|
158
|
+
throw new Error(`${vendorLabel} request error ${frame.status}: ${(_g = frame.error) !== null && _g !== void 0 ? _g : JSON.stringify(frame.details)}`);
|
|
134
159
|
}
|
|
135
160
|
return (yield response.json());
|
|
136
161
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genesislcap/foundation-ai",
|
|
3
3
|
"description": "Genesis Foundation AI - Provider-agnostic AI configuration and shared utilities",
|
|
4
|
-
"version": "15.8.
|
|
4
|
+
"version": "15.8.3",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"license": "SEE LICENSE IN license.txt",
|
|
7
7
|
"main": "dist/esm/index.js",
|
|
@@ -52,17 +52,17 @@
|
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@genesislcap/foundation-testing": "15.8.
|
|
56
|
-
"@genesislcap/genx": "15.8.
|
|
57
|
-
"@genesislcap/rollup-builder": "15.8.
|
|
58
|
-
"@genesislcap/ts-builder": "15.8.
|
|
59
|
-
"@genesislcap/uvu-playwright-builder": "15.8.
|
|
60
|
-
"@genesislcap/vite-builder": "15.8.
|
|
61
|
-
"@genesislcap/webpack-builder": "15.8.
|
|
55
|
+
"@genesislcap/foundation-testing": "15.8.3",
|
|
56
|
+
"@genesislcap/genx": "15.8.3",
|
|
57
|
+
"@genesislcap/rollup-builder": "15.8.3",
|
|
58
|
+
"@genesislcap/ts-builder": "15.8.3",
|
|
59
|
+
"@genesislcap/uvu-playwright-builder": "15.8.3",
|
|
60
|
+
"@genesislcap/vite-builder": "15.8.3",
|
|
61
|
+
"@genesislcap/webpack-builder": "15.8.3"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@genesislcap/foundation-logger": "15.8.
|
|
65
|
-
"@genesislcap/foundation-utils": "15.8.
|
|
64
|
+
"@genesislcap/foundation-logger": "15.8.3",
|
|
65
|
+
"@genesislcap/foundation-utils": "15.8.3",
|
|
66
66
|
"@microsoft/fast-foundation": "2.50.0"
|
|
67
67
|
},
|
|
68
68
|
"repository": {
|
|
@@ -73,5 +73,5 @@
|
|
|
73
73
|
"publishConfig": {
|
|
74
74
|
"access": "public"
|
|
75
75
|
},
|
|
76
|
-
"gitHead": "
|
|
76
|
+
"gitHead": "0001fb4c5a056b22bb1a3d316cc4f2e2aa79cff4"
|
|
77
77
|
}
|