@apifuse/provider-sdk 2.2.0-beta.67 → 2.2.0-beta.69
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/AUTHORING.md +34 -2
- package/CHANGELOG.md +8 -0
- package/README.md +18 -7
- package/bin/apifuse-record.ts +81 -21
- package/bin/apifuse-submit-check.ts +170 -28
- package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +138 -23
- package/dist/cli/templates/provider/README.md.tpl +17 -9
- package/dist/config/loader.d.ts +16 -0
- package/dist/config/loader.js +25 -8
- package/dist/error-resolution.d.ts +13 -0
- package/dist/error-resolution.js +72 -35
- package/dist/fixture-sanitization.js +159 -15
- package/dist/lint.d.ts +2 -0
- package/dist/lint.js +549 -11
- package/dist/runtime/diagnostic-redactor.d.ts +6 -1
- package/dist/runtime/diagnostic-redactor.js +16 -14
- package/dist/runtime/executor.js +3 -0
- package/dist/runtime/http.d.ts +2 -0
- package/dist/runtime/http.js +17 -103
- package/dist/runtime/instrumentation.js +4 -1
- package/dist/runtime/provider.d.ts +7 -0
- package/dist/runtime/provider.js +14 -0
- package/dist/runtime/redirect-hop-headers.d.ts +19 -0
- package/dist/runtime/redirect-hop-headers.js +58 -0
- package/dist/runtime/redirects.d.ts +33 -1
- package/dist/runtime/redirects.js +113 -0
- package/dist/runtime/resolver-span-attributes.d.ts +34 -0
- package/dist/runtime/resolver-span-attributes.js +93 -0
- package/dist/runtime/resolver-usage.d.ts +8 -0
- package/dist/runtime/resolver-usage.js +5 -11
- package/dist/runtime/resolver-vendors/browser.js +5 -4
- package/dist/runtime/resolver-vendors/capsolver.js +11 -10
- package/dist/runtime/resolver-vendors/twocaptcha.js +9 -8
- package/dist/runtime/resolver.js +7 -6
- package/dist/runtime/stealth-owned-headers.d.ts +21 -8
- package/dist/runtime/stealth-owned-headers.js +28 -8
- package/dist/runtime/stealth-telemetry.d.ts +1 -1
- package/dist/runtime/stealth.js +158 -46
- package/dist/runtime/trace.js +2 -2
- package/dist/server/index.d.ts +1 -0
- package/dist/server/serve-implementation.d.ts +2 -1
- package/dist/server/serve-implementation.js +45 -30
- package/dist/server/state-backend-report.d.ts +70 -0
- package/dist/server/state-backend-report.js +113 -0
- package/dist/testing/run.js +14 -7
- package/dist/trace-sanitization.js +1 -1
- package/dist/types.d.ts +30 -6
- package/package.json +2 -2
- package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +138 -23
- package/src/cli/templates/provider/README.md.tpl +17 -9
- package/src/config/loader.ts +49 -12
- package/src/error-resolution.ts +89 -35
- package/src/fixture-sanitization.ts +175 -17
- package/src/lint.ts +595 -10
- package/src/runtime/diagnostic-redactor.ts +19 -13
- package/src/runtime/executor.ts +3 -0
- package/src/runtime/http.ts +24 -112
- package/src/runtime/instrumentation.ts +4 -1
- package/src/runtime/provider.ts +15 -0
- package/src/runtime/redirect-hop-headers.ts +71 -0
- package/src/runtime/redirects.ts +150 -1
- package/src/runtime/resolver-span-attributes.ts +139 -0
- package/src/runtime/resolver-usage.ts +8 -5
- package/src/runtime/resolver-vendors/browser.ts +5 -4
- package/src/runtime/resolver-vendors/capsolver.ts +11 -10
- package/src/runtime/resolver-vendors/twocaptcha.ts +9 -8
- package/src/runtime/resolver.ts +7 -6
- package/src/runtime/stealth-owned-headers.ts +34 -8
- package/src/runtime/stealth-telemetry.ts +6 -1
- package/src/runtime/stealth.ts +180 -58
- package/src/runtime/trace.ts +6 -2
- package/src/server/index.ts +4 -0
- package/src/server/serve-implementation.ts +58 -31
- package/src/server/state-backend-report.ts +185 -0
- package/src/testing/run.ts +15 -2
- package/src/trace-sanitization.ts +1 -1
- package/src/types.ts +30 -6
package/dist/runtime/stealth.js
CHANGED
|
@@ -5,11 +5,12 @@ import { SDKError, TransportError } from "../errors.js";
|
|
|
5
5
|
import { DEFAULT_STEALTH_BROWSER, DEFAULT_STEALTH_OS, getStealthProfile, resolverClientProfileFamily, resolveStealthProfileSelection, } from "../stealth/profiles.js";
|
|
6
6
|
import { chrome149HeaderOrder } from "./chrome149-header-order.js";
|
|
7
7
|
import { registerDiagnosticValue } from "./diagnostic-env.js";
|
|
8
|
-
import {
|
|
8
|
+
import { assertCallerHeadersSupported } from "./stealth-owned-headers.js";
|
|
9
9
|
import { ENGINE_CEREMONY_EGRESS_LEASE, } from "./egress-lease.js";
|
|
10
10
|
import { createProxyAuthIpDeniedError, createProxyEdgeAuthRejectedError, createProxyEdgeTlsRejectedError, createProxyPoolExhaustedError, createProxyPoolStaleError, isProxyAuthIpDeniedMessage, isProxyEdgeAuthRejectedMessage, isProxyEdgeTlsRejectedResponse, isProxyPoolRefreshableError, isProxyPoolStaleMessage, isProxyPoolStaleStatus, PROXY_EDGE_AUTH_REJECTED_CODE, PROXY_POOL_STALE_CODE, } from "./proxy-errors.js";
|
|
11
11
|
import { computeProxyAttemptIndex, computeProxyTransportRetryDelayMs, createDefaultProxyTransportRetryOptions, isProxyTransportRetryMethod, normalizeProxyTransportRetryOptions, shouldRetryProxyTransportAttempt, validateUnsafeProxyTransportRetryMethods, } from "./proxy-retry-policy.js";
|
|
12
|
-
import {
|
|
12
|
+
import { redirectHopHeaders } from "./redirect-hop-headers.js";
|
|
13
|
+
import { evaluateRedirectHop, invalidRedirectPolicy, isRedirectStatus, nextRedirectMethod, nextSameOriginRedirectHop, normalizeRedirectPolicy, resolveRedirectUrl, } from "./redirects.js";
|
|
13
14
|
import { isSensitiveKey, normalizeSensitiveParams, redactSensitiveError, redactSensitiveRequestError, redactSensitiveText, redactUrlQueryParams, serializeRequestUrl, } from "./request-options.js";
|
|
14
15
|
import { akamaiSbsdChallengeKey, detectAkamaiSbsdChallenge, } from "./stealth-akamai-sbsd.js";
|
|
15
16
|
import { StealthCookieJar } from "./stealth-cookies.js";
|
|
@@ -37,12 +38,10 @@ function isBoundEgressTransportFailure(error) {
|
|
|
37
38
|
(error.code !== undefined && BOUND_EGRESS_TRANSPORT_FAILURE_CODES.has(error.code)));
|
|
38
39
|
}
|
|
39
40
|
const MAX_STEALTH_REDIRECT_HOPS = 10;
|
|
40
|
-
const
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"content-type",
|
|
45
|
-
]);
|
|
41
|
+
const STEALTH_REDIRECT_POLICY_TRANSPORT = {
|
|
42
|
+
label: "ctx.stealth",
|
|
43
|
+
maxHops: MAX_STEALTH_REDIRECT_HOPS,
|
|
44
|
+
};
|
|
46
45
|
function isRecord(value) {
|
|
47
46
|
return typeof value === "object" && value !== null;
|
|
48
47
|
}
|
|
@@ -179,6 +178,8 @@ function normalizedHeaderEntries(headers) {
|
|
|
179
178
|
// installs Accept-Language via DevTools next to User-Agent; real Chrome only
|
|
180
179
|
// receives it from //net (URLRequestHttpJob::AddExtraHeaders), after
|
|
181
180
|
// Accept-Encoding and before Cookie, as al-placement-capture.json B/C show.
|
|
181
|
+
// chrome-form-post-capture.json was taken without that option and needs no
|
|
182
|
+
// adjustment.
|
|
182
183
|
const CHROME_HEADER_ORDERS = {
|
|
183
184
|
navigation: [
|
|
184
185
|
"sec-ch-ua",
|
|
@@ -215,6 +216,27 @@ const CHROME_HEADER_ORDERS = {
|
|
|
215
216
|
"cookie",
|
|
216
217
|
"priority",
|
|
217
218
|
],
|
|
219
|
+
"form-post": [
|
|
220
|
+
"content-length",
|
|
221
|
+
"cache-control",
|
|
222
|
+
"sec-ch-ua",
|
|
223
|
+
"sec-ch-ua-mobile",
|
|
224
|
+
"sec-ch-ua-platform",
|
|
225
|
+
"upgrade-insecure-requests",
|
|
226
|
+
"content-type",
|
|
227
|
+
"user-agent",
|
|
228
|
+
"origin",
|
|
229
|
+
"accept",
|
|
230
|
+
"sec-fetch-site",
|
|
231
|
+
"sec-fetch-mode",
|
|
232
|
+
"sec-fetch-user",
|
|
233
|
+
"sec-fetch-dest",
|
|
234
|
+
"referer",
|
|
235
|
+
"accept-encoding",
|
|
236
|
+
"accept-language",
|
|
237
|
+
"cookie",
|
|
238
|
+
"priority",
|
|
239
|
+
],
|
|
218
240
|
};
|
|
219
241
|
const CHROME_H1_HEADER_ORDERS = {
|
|
220
242
|
navigation: [
|
|
@@ -271,6 +293,28 @@ const CHROME_H1_HEADER_ORDERS = {
|
|
|
271
293
|
"accept-language",
|
|
272
294
|
"cookie",
|
|
273
295
|
],
|
|
296
|
+
"form-post": [
|
|
297
|
+
"host",
|
|
298
|
+
"connection",
|
|
299
|
+
"content-length",
|
|
300
|
+
"cache-control",
|
|
301
|
+
"sec-ch-ua",
|
|
302
|
+
"sec-ch-ua-mobile",
|
|
303
|
+
"sec-ch-ua-platform",
|
|
304
|
+
"upgrade-insecure-requests",
|
|
305
|
+
"content-type",
|
|
306
|
+
"user-agent",
|
|
307
|
+
"origin",
|
|
308
|
+
"accept",
|
|
309
|
+
"sec-fetch-site",
|
|
310
|
+
"sec-fetch-mode",
|
|
311
|
+
"sec-fetch-user",
|
|
312
|
+
"sec-fetch-dest",
|
|
313
|
+
"referer",
|
|
314
|
+
"accept-encoding",
|
|
315
|
+
"accept-language",
|
|
316
|
+
"cookie",
|
|
317
|
+
],
|
|
274
318
|
};
|
|
275
319
|
const CHROME_H1_HEADER_NAMES = {
|
|
276
320
|
host: "Host",
|
|
@@ -293,6 +337,25 @@ const CHROME_H1_HEADER_NAMES = {
|
|
|
293
337
|
origin: "Origin",
|
|
294
338
|
range: "Range",
|
|
295
339
|
};
|
|
340
|
+
/**
|
|
341
|
+
* What each request class sends besides its header order, one row per public
|
|
342
|
+
* `stealth.requestClass`:
|
|
343
|
+
*
|
|
344
|
+
* - `fetchMetadata: "navigate"` is a document navigation: `Sec-Fetch-Mode:
|
|
345
|
+
* navigate`, `Sec-Fetch-Dest: document`, `Sec-Fetch-User: ?1` unless
|
|
346
|
+
* `userActivation` is false, `Upgrade-Insecure-Requests: 1`, and the
|
|
347
|
+
* profile's `Accept` and `Priority`. `"cors"` is a script fetch: `cors` /
|
|
348
|
+
* `empty`, `Accept: *\/*`, `Priority: u=1, i`.
|
|
349
|
+
* - `bodyful` sends `Content-Length`.
|
|
350
|
+
* - `validateCache` sends `Cache-Control: max-age=0`, which Chrome adds to a
|
|
351
|
+
* form submission (chrome-form-post-capture.json).
|
|
352
|
+
*/
|
|
353
|
+
const CHROME_REQUEST_CLASSES = {
|
|
354
|
+
navigation: { fetchMetadata: "navigate", bodyful: false, validateCache: false },
|
|
355
|
+
"form-post": { fetchMetadata: "navigate", bodyful: true, validateCache: true },
|
|
356
|
+
xhr: { fetchMetadata: "cors", bodyful: false, validateCache: false },
|
|
357
|
+
post: { fetchMetadata: "cors", bodyful: true, validateCache: false },
|
|
358
|
+
};
|
|
296
359
|
function chromeHeaderOrder(requestClass, isHttp1, caller) {
|
|
297
360
|
if (isHttp1)
|
|
298
361
|
return CHROME_H1_HEADER_ORDERS[requestClass];
|
|
@@ -320,29 +383,11 @@ function chromeHeaderOrder(requestClass, isHttp1, caller) {
|
|
|
320
383
|
}
|
|
321
384
|
return order;
|
|
322
385
|
}
|
|
323
|
-
function normalizedCallerHeaderEntries(headers) {
|
|
324
|
-
const entries = normalizedHeaderEntries(headers);
|
|
325
|
-
assertCallerHeadersSupported(entries);
|
|
326
|
-
return entries;
|
|
327
|
-
}
|
|
328
386
|
function normalizedCallerHeaderEntriesFromRecord(headers) {
|
|
329
387
|
// The record has been through normalizeHeaders (lowercase, merged names); the
|
|
330
388
|
// lowercase here only guards later insertions such as the cookie jar's header.
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
value,
|
|
334
|
-
]);
|
|
335
|
-
assertCallerHeadersSupported(entries);
|
|
336
|
-
return entries;
|
|
337
|
-
}
|
|
338
|
-
function assertCallerHeadersSupported(entries) {
|
|
339
|
-
for (const [name] of entries) {
|
|
340
|
-
if (isStealthOwnedHeaderName(name)) {
|
|
341
|
-
throw new SDKError(`Stealth transport owns the "${name}" header; remove it from headers.`, {
|
|
342
|
-
code: "STEALTH_HEADER_OVERRIDE_UNSUPPORTED",
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
}
|
|
389
|
+
assertCallerHeadersSupported(headers);
|
|
390
|
+
return Object.entries(headers).map(([name, value]) => [name.toLowerCase(), value]);
|
|
346
391
|
}
|
|
347
392
|
function chromeRequestClass(method, requestedClass) {
|
|
348
393
|
if (requestedClass)
|
|
@@ -374,9 +419,10 @@ function buildChromeHeaderTuples(options) {
|
|
|
374
419
|
const caller = new Map(callerEntries);
|
|
375
420
|
const emulation = new Map(Array.from(options.emulationHeaders, ([name, value]) => [name.toLowerCase(), value]));
|
|
376
421
|
const requestClass = chromeRequestClass(options.method, options.requestClass);
|
|
422
|
+
const shape = CHROME_REQUEST_CLASSES[requestClass];
|
|
377
423
|
const referer = caller.get("referer");
|
|
378
424
|
const fetchSite = secFetchSite(options.requestUrl, referer);
|
|
379
|
-
const isNavigation =
|
|
425
|
+
const isNavigation = shape.fetchMetadata === "navigate";
|
|
380
426
|
const values = new Map([
|
|
381
427
|
["sec-ch-ua", requiredEmulationHeader(emulation, "sec-ch-ua")],
|
|
382
428
|
["sec-ch-ua-mobile", requiredEmulationHeader(emulation, "sec-ch-ua-mobile")],
|
|
@@ -403,6 +449,8 @@ function buildChromeHeaderTuples(options) {
|
|
|
403
449
|
if (options.userActivation !== false)
|
|
404
450
|
values.set("sec-fetch-user", "?1");
|
|
405
451
|
}
|
|
452
|
+
if (shape.validateCache)
|
|
453
|
+
values.set("cache-control", "max-age=0");
|
|
406
454
|
for (const [name, value] of callerEntries)
|
|
407
455
|
values.set(name, value);
|
|
408
456
|
for (const name of ["content-type", "origin", "referer"]) {
|
|
@@ -410,7 +458,10 @@ function buildChromeHeaderTuples(options) {
|
|
|
410
458
|
if (value !== undefined)
|
|
411
459
|
values.set(name, value);
|
|
412
460
|
}
|
|
413
|
-
|
|
461
|
+
// A bodyful class keeps its class across a 301/302/303 that rewrites the method
|
|
462
|
+
// to GET (a form submission's redirect stays a validated navigation), but the
|
|
463
|
+
// rewritten request has no body and Chrome sends no Content-Length for it.
|
|
464
|
+
if (shape.bodyful && options.method !== "GET" && options.method !== "HEAD") {
|
|
414
465
|
values.set("content-length", caller.get("content-length") ?? String(Buffer.byteLength(options.body ?? "")));
|
|
415
466
|
}
|
|
416
467
|
const isHttp1 = new URL(options.requestUrl).protocol === "http:";
|
|
@@ -865,8 +916,25 @@ function locationHeader(headers) {
|
|
|
865
916
|
}
|
|
866
917
|
return undefined;
|
|
867
918
|
}
|
|
868
|
-
|
|
869
|
-
|
|
919
|
+
/**
|
|
920
|
+
* Snapshot a caller's `redirectPolicy` before the first send. The policy acts
|
|
921
|
+
* on redirects the transport follows, so it requires the default
|
|
922
|
+
* `redirect: "follow"`.
|
|
923
|
+
*/
|
|
924
|
+
function snapshotStealthRedirectPolicy(options) {
|
|
925
|
+
let policy;
|
|
926
|
+
try {
|
|
927
|
+
policy = normalizeRedirectPolicy(options.redirectPolicy, STEALTH_REDIRECT_POLICY_TRANSPORT);
|
|
928
|
+
}
|
|
929
|
+
catch (error) {
|
|
930
|
+
if (error instanceof TransportError)
|
|
931
|
+
throw error;
|
|
932
|
+
throw invalidRedirectPolicy(STEALTH_REDIRECT_POLICY_TRANSPORT, "could not be read safely", error instanceof Error ? error : undefined);
|
|
933
|
+
}
|
|
934
|
+
if (policy && options.redirect !== undefined && options.redirect !== "follow") {
|
|
935
|
+
throw invalidRedirectPolicy(STEALTH_REDIRECT_POLICY_TRANSPORT, `it applies to followed redirects and cannot be combined with redirect: "${options.redirect}"`);
|
|
936
|
+
}
|
|
937
|
+
return policy;
|
|
870
938
|
}
|
|
871
939
|
function assertStealthRedirectUrl(url) {
|
|
872
940
|
const protocol = new URL(url).protocol;
|
|
@@ -962,7 +1030,9 @@ function startStealthExchange(telemetry, method, options, signal) {
|
|
|
962
1030
|
kind: telemetry.kind,
|
|
963
1031
|
requestClass: requestClass === "navigation" && options.stealth?.userActivation === false
|
|
964
1032
|
? "script_navigation"
|
|
965
|
-
: requestClass
|
|
1033
|
+
: requestClass === "form-post"
|
|
1034
|
+
? "form_post"
|
|
1035
|
+
: requestClass,
|
|
966
1036
|
...(status === undefined ? {} : { status }),
|
|
967
1037
|
...(errorCode ? { errorCode } : {}),
|
|
968
1038
|
...(error === undefined
|
|
@@ -988,6 +1058,10 @@ async function fetchStealthRedirectChain(transport, cookieJar, requestUrl, metho
|
|
|
988
1058
|
let followedHops = 0;
|
|
989
1059
|
let response;
|
|
990
1060
|
const deadline = options.timeout ? performance.now() + options.timeout : undefined;
|
|
1061
|
+
// Snapshotted and validated by session.fetch before the first send.
|
|
1062
|
+
const redirectPolicy = options.redirectPolicy;
|
|
1063
|
+
const initialOrigin = new URL(requestUrl).origin;
|
|
1064
|
+
const visitedRequests = new Set([`${method} ${requestUrl}`]);
|
|
991
1065
|
let finishExchange;
|
|
992
1066
|
let status;
|
|
993
1067
|
let exchangeError;
|
|
@@ -1040,23 +1114,42 @@ async function fetchStealthRedirectChain(transport, cookieJar, requestUrl, metho
|
|
|
1040
1114
|
status: 0,
|
|
1041
1115
|
});
|
|
1042
1116
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1117
|
+
let nextUrl;
|
|
1118
|
+
let nextMethod;
|
|
1119
|
+
if (redirectPolicy) {
|
|
1120
|
+
discardStealthRedirectBody(response);
|
|
1121
|
+
({ nextUrl, nextMethod } = nextSameOriginRedirectHop({
|
|
1122
|
+
policy: redirectPolicy,
|
|
1123
|
+
initialOrigin,
|
|
1124
|
+
currentUrl,
|
|
1125
|
+
responseUrl: response.url ?? currentUrl,
|
|
1126
|
+
status: response.status,
|
|
1127
|
+
method: currentMethod,
|
|
1128
|
+
location: response.headers.get("location") ?? undefined,
|
|
1129
|
+
followedHops,
|
|
1130
|
+
visitedRequests,
|
|
1131
|
+
}));
|
|
1132
|
+
}
|
|
1133
|
+
else {
|
|
1134
|
+
const location = resolveRedirectUrl(response.headers.get("location") ?? undefined, response.url ?? currentUrl);
|
|
1135
|
+
if (!location)
|
|
1136
|
+
break;
|
|
1137
|
+
if (followedHops >= MAX_STEALTH_REDIRECT_HOPS) {
|
|
1138
|
+
discardStealthRedirectBody(response);
|
|
1139
|
+
throw new TransportError(`Stealth request exceeded the ${MAX_STEALTH_REDIRECT_HOPS}-redirect limit`, { code: "transport_network_error", status: 0 });
|
|
1140
|
+
}
|
|
1047
1141
|
discardStealthRedirectBody(response);
|
|
1048
|
-
|
|
1142
|
+
nextUrl = location;
|
|
1143
|
+
nextMethod = nextRedirectMethod(response.status, currentMethod);
|
|
1049
1144
|
}
|
|
1050
1145
|
assertStealthRedirectUrl(nextUrl);
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
if (nextMethod !== currentMethod) {
|
|
1146
|
+
currentHeaders = redirectHopHeaders({ from: currentUrl, to: nextUrl, methodChanged: nextMethod !== currentMethod }, currentHeaders);
|
|
1147
|
+
if (nextMethod !== currentMethod)
|
|
1054
1148
|
currentBody = undefined;
|
|
1055
|
-
currentHeaders = withoutRedirectBodyHeaders(currentHeaders);
|
|
1056
|
-
}
|
|
1057
1149
|
currentMethod = nextMethod;
|
|
1058
1150
|
currentUrl = nextUrl;
|
|
1059
1151
|
followedHops += 1;
|
|
1152
|
+
visitedRequests.add(`${currentMethod} ${currentUrl}`);
|
|
1060
1153
|
emitStealthTelemetry(telemetry?.sink, (sink) => sink.recordRedirectHop());
|
|
1061
1154
|
finishExchange(status);
|
|
1062
1155
|
finishExchange = undefined;
|
|
@@ -1155,10 +1248,15 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
1155
1248
|
let entry = clients.get(cacheKey);
|
|
1156
1249
|
if (!entry) {
|
|
1157
1250
|
entry = {
|
|
1251
|
+
// The header shape only partitions sessions; it is never handed to wreq as
|
|
1252
|
+
// session `defaultHeaders`. wreq merges those into every request that lacks
|
|
1253
|
+
// one of their names, so a header the SDK left off on purpose — a credential
|
|
1254
|
+
// dropped on a cross-origin redirect hop, the body headers after POST -> GET,
|
|
1255
|
+
// a cookie for another host — would come back with the first request's value.
|
|
1256
|
+
// Every request already sends its complete ordered header list.
|
|
1158
1257
|
session: wreq.createSession({
|
|
1159
1258
|
browser,
|
|
1160
1259
|
os,
|
|
1161
|
-
...(defaultHeaders ? { defaultHeaders } : {}),
|
|
1162
1260
|
...(proxyUrl ? { proxy: proxyUrl } : {}),
|
|
1163
1261
|
...(ignoreTlsErrors ? { insecure: true } : {}),
|
|
1164
1262
|
timeout: 30_000,
|
|
@@ -1332,7 +1430,12 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
1332
1430
|
const { hasExplicitRetryPolicy, method, stealthRetryOptions } = (() => {
|
|
1333
1431
|
try {
|
|
1334
1432
|
const method = normalizeMethod(options.method ?? "GET");
|
|
1335
|
-
|
|
1433
|
+
assertCallerHeadersSupported(options.headers ?? {});
|
|
1434
|
+
// Replace the caller's object with the validated copy: the walker, a
|
|
1435
|
+
// retry and an explicit replay all read this snapshot.
|
|
1436
|
+
const redirectPolicy = snapshotStealthRedirectPolicy(options);
|
|
1437
|
+
if (redirectPolicy)
|
|
1438
|
+
options.redirectPolicy = redirectPolicy;
|
|
1336
1439
|
const hasExplicitRetryPolicy = options.retry !== undefined;
|
|
1337
1440
|
const stealthRetryOptions = normalizeProxyTransportRetryOptions(options.retry, {
|
|
1338
1441
|
extraErrorCodes: STEALTH_PROXY_TRANSPORT_RETRY_ERROR_CODES,
|
|
@@ -1900,10 +2003,11 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
1900
2003
|
const maxHops = options.maxHops === undefined || !Number.isFinite(options.maxHops)
|
|
1901
2004
|
? 10
|
|
1902
2005
|
: Math.max(0, Math.floor(options.maxHops));
|
|
1903
|
-
const { url: _url, maxHops: _maxHops, stopWhen, params, sensitiveParams, ...fetchOptions } = options;
|
|
2006
|
+
const { url: _url, maxHops: _maxHops, stopWhen, params, sensitiveParams, headers: initialHeaders, ...fetchOptions } = options;
|
|
1904
2007
|
const hops = [];
|
|
1905
2008
|
let method = normalizeMethod(options.method ?? "GET");
|
|
1906
2009
|
let body = options.body;
|
|
2010
|
+
let headers = initialHeaders;
|
|
1907
2011
|
let response;
|
|
1908
2012
|
const visitedRequests = new Set();
|
|
1909
2013
|
const initialParams = params
|
|
@@ -1956,6 +2060,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
1956
2060
|
try {
|
|
1957
2061
|
response = await session.fetch(currentUrl, {
|
|
1958
2062
|
...fetchOptions,
|
|
2063
|
+
...(headers ? { headers } : {}),
|
|
1959
2064
|
body,
|
|
1960
2065
|
method,
|
|
1961
2066
|
...(hopIndex === 0 && initialParams ? { params: initialParams } : {}),
|
|
@@ -2042,6 +2147,13 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
2042
2147
|
cookieStore: cookieJar.serialize(),
|
|
2043
2148
|
};
|
|
2044
2149
|
}
|
|
2150
|
+
if (headers) {
|
|
2151
|
+
headers = redirectHopHeaders({
|
|
2152
|
+
from: currentUrl,
|
|
2153
|
+
to: decision.nextUrl,
|
|
2154
|
+
methodChanged: decision.nextMethod !== method,
|
|
2155
|
+
}, headers);
|
|
2156
|
+
}
|
|
2045
2157
|
if (decision.nextMethod !== method) {
|
|
2046
2158
|
body = undefined;
|
|
2047
2159
|
}
|
package/dist/runtime/trace.js
CHANGED
|
@@ -76,7 +76,7 @@ function normalizeAttributes(attributes, redact, retainBigints = false) {
|
|
|
76
76
|
? redactedKey === REDACTION_FAILED
|
|
77
77
|
? REDACTION_FAILED
|
|
78
78
|
: REDACTED_QUERY_VALUE
|
|
79
|
-
: normalizeAttributeValue(value, diagnosticAttributeRedactor(key, redact), retainBigints);
|
|
79
|
+
: normalizeAttributeValue(value, diagnosticAttributeRedactor(key, redact, value), retainBigints);
|
|
80
80
|
return [normalizedKey, normalizedValue];
|
|
81
81
|
})
|
|
82
82
|
.filter((entry) => entry[1] !== undefined);
|
|
@@ -103,7 +103,7 @@ function prepareResourceAttributesForExport(attributes, redact, sanitize) {
|
|
|
103
103
|
? redactedKey === REDACTION_FAILED
|
|
104
104
|
? REDACTION_FAILED
|
|
105
105
|
: REDACTED_QUERY_VALUE
|
|
106
|
-
: sanitizeSpanNameForOutput(value, diagnosticAttributeRedactor(key, redact)),
|
|
106
|
+
: sanitizeSpanNameForOutput(value, diagnosticAttributeRedactor(key, redact, value)),
|
|
107
107
|
];
|
|
108
108
|
}));
|
|
109
109
|
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export type { ResolverVendorUnavailableReason } from "../runtime/resolver-vendor
|
|
|
12
12
|
export type { Span, TraceContext } from "../runtime/trace.js";
|
|
13
13
|
export type { StealthBrowser, StealthOS, StealthProfileDescriptor } from "../types.js";
|
|
14
14
|
export { createServerApp, createServerAppAsync, ERROR_OBSERVABILITY_HEADER, type ErrorObservabilityDetails, type ProviderServerCloseOptions, type ProviderServerHandle, type ProviderErrorCauseFrame, type ProviderServerLogEvent, type ProviderServerLogger, type ProviderServerOperationExecutor, type ProviderServerOperationExecutorInput, type ProviderServerOptions, type ProviderServerStatefulForwardEnvelope, type ServeOptions, serve, } from "./serve.js";
|
|
15
|
+
export type { ProviderStateBackendLogEvent, ProviderStoreBackendReport, } from "./state-backend-report.js";
|
|
15
16
|
export type { ProviderErrorObservability } from "../errors.js";
|
|
16
17
|
export { computeSelfTestPlanDigest, createSelfTestApp, createSelfTestAuthFlowInvoke, createSelfTestInvoke, DEFAULT_SELF_TEST_REQUEST_BUDGET_MS, isSelfTestReadOnlyOperation, PROVIDER_RUNTIME_SELF_TEST_REQUEST_BUDGET_MS_ENV, resolveSelfTestPort, SELF_TEST_AUTH_FLOW_MULTI_TURN_SKIP_REASON, SELF_TEST_AUTH_FLOW_REJECTED_SKIP_REASON, SELF_TEST_HEALTHZ_PATH, SELF_TEST_PATH, SELF_TEST_SCHEMA_VERSION, type SelfTestAppOptions, type SelfTestAuthFlowInvoke, type SelfTestAuthFlowRoute, type SelfTestCaseResult, type SelfTestCaseStatus, type SelfTestOperationInvoke, type SelfTestRequest, SelfTestRequestSchema, type SelfTestResponse, } from "./self-test.js";
|
|
17
18
|
export { type InputDateTokenCalendar, resolveHealthCheckInputDateTokens, } from "./self-test-input-tokens.js";
|
|
@@ -10,6 +10,7 @@ import { type ProxyTelemetryLogPayload } from "../runtime/proxy-telemetry.js";
|
|
|
10
10
|
import { type ResolverTelemetryLogPayload } from "../runtime/resolver-telemetry.js";
|
|
11
11
|
import type { OcrContext, ProviderContext, ProviderDefinition, ProviderFilesContext, ProviderRuntimeState, ProviderRuntimeTarget, ResolverContext, SttContext } from "../types.js";
|
|
12
12
|
import type { SelfTestCancellationLogEvent } from "./self-test.js";
|
|
13
|
+
import { type ProviderStateBackendLogEvent } from "./state-backend-report.js";
|
|
13
14
|
import { type AuthFlowRequest, type OperationRequest } from "./types.js";
|
|
14
15
|
/** Compact SDK-owned error classification emitted separately from the public response body. */
|
|
15
16
|
export declare const ERROR_OBSERVABILITY_HEADER = "X-ApiFuse-Error-Observability";
|
|
@@ -167,7 +168,7 @@ export type ProviderServerLogEvent = (ProviderServerLogEventBase & {
|
|
|
167
168
|
* `engine_env_ignored`. Absent when the selection was unambiguous.
|
|
168
169
|
*/
|
|
169
170
|
warnings?: readonly string[];
|
|
170
|
-
} | SelfTestCancellationLogEvent;
|
|
171
|
+
} | ProviderStateBackendLogEvent | SelfTestCancellationLogEvent;
|
|
171
172
|
export type ProviderServerLogger = (event: ProviderServerLogEvent) => void;
|
|
172
173
|
export type ProviderServerOptions<TContext extends Partial<ProviderContext> = ProviderContext> = {
|
|
173
174
|
logger?: ProviderServerLogger;
|
|
@@ -15,7 +15,7 @@ import { createEngineForMode, isUnavailableProviderEngine, resolveProviderEngine
|
|
|
15
15
|
import { SDK_VERSION } from "./sdk-version.js";
|
|
16
16
|
import { safeProviderErrorObservability } from "../error-observability.js";
|
|
17
17
|
import { providerErrorStackFrames } from "./error-stack-frames.js";
|
|
18
|
-
import { SDK_OWNED_PROVIDER_ERROR_CODES, SDK_RUNTIME_OWNED_ERROR_CODES, SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES, } from "../error-resolution.js";
|
|
18
|
+
import { registeredUpstreamErrorCategory, SDK_OWNED_PROVIDER_ERROR_CODES, SDK_RUNTIME_OWNED_ERROR_CODES, SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES, } from "../error-resolution.js";
|
|
19
19
|
import { AuthError, isProviderError, isSessionExpiredError, isTransportError, isValidationError, ProviderError, SDKError, } from "../errors.js";
|
|
20
20
|
import { sanitizeDiagnosticText } from "../fixture-sanitization.js";
|
|
21
21
|
import { loadProviderLocaleCatalogs, localizeAuthTurn, } from "../i18n/catalog.js";
|
|
@@ -37,10 +37,11 @@ import { HttpTelemetryCollector } from "../runtime/http-telemetry.js";
|
|
|
37
37
|
import { wrapWithInstrumentation } from "../runtime/instrumentation.js";
|
|
38
38
|
import { createOcrClientFromEnv } from "../runtime/ocr.js";
|
|
39
39
|
import { bindOcrTelemetry, OcrTelemetryCollector } from "../runtime/ocr-telemetry.js";
|
|
40
|
-
import { getProviderBaseUrl } from "../runtime/provider.js";
|
|
40
|
+
import { getProviderBaseUrl, getProviderStealthBaseUrl } from "../runtime/provider.js";
|
|
41
41
|
import { PROXY_AUTH_IP_DENIED_CODE, PROXY_EDGE_AUTH_REJECTED_CODE, PROXY_POOL_EXHAUSTED_CODE, } from "../runtime/proxy-errors.js";
|
|
42
42
|
import { PROVIDER_TELEMETRY_HEADER, ProxyTelemetryCollector, } from "../runtime/proxy-telemetry.js";
|
|
43
43
|
import { closedEnum, RequestTelemetry, tenantOpaqueKeys, } from "../runtime/request-telemetry.js";
|
|
44
|
+
import { resolverSpanAttributes } from "../runtime/resolver-span-attributes.js";
|
|
44
45
|
import { bindResolverTelemetry, createUnsupportedResolverClient, inheritResolverTelemetryBinding, } from "../runtime/resolver-shared.js";
|
|
45
46
|
import { ResolverTelemetryCollector, } from "../runtime/resolver-telemetry.js";
|
|
46
47
|
import { createResolverRuntimeOptions, resolveNativeProxyPolicy, } from "../runtime/resolver-runtime-options.js";
|
|
@@ -62,6 +63,7 @@ import { getStealthProfile } from "../stealth/profiles.js";
|
|
|
62
63
|
import { APIFUSE_STREAM_DONE_EVENT, APIFUSE_STREAM_ERROR_EVENT, encodeSseEvent, error as streamError, } from "../stream.js";
|
|
63
64
|
import { sanitizeSpanNameForOutput, sanitizeTraceAttributes } from "../trace-sanitization.js";
|
|
64
65
|
import { VALID_OPERATION_ERROR_STATUSES } from "../types.js";
|
|
66
|
+
import { providerStateBackendLogEvent, } from "./state-backend-report.js";
|
|
65
67
|
import { resolveSelfTestMasterSecrets } from "./self-test-token.js";
|
|
66
68
|
import { collectStaticDiagnosticSensitiveValues, createDiagnosticEnvObserver, } from "./sensitive-values.js";
|
|
67
69
|
import { resolveServerTraceContextOptions } from "./trace-output.js";
|
|
@@ -340,14 +342,6 @@ function bindResolverSignalWithoutRuntime(resolver, defaultSignal) {
|
|
|
340
342
|
inheritResolverTelemetryBinding(resolver, boundResolver);
|
|
341
343
|
return boundResolver;
|
|
342
344
|
}
|
|
343
|
-
function getProviderStealthBaseUrl(provider) {
|
|
344
|
-
const baseUrl = getProviderBaseUrl(provider);
|
|
345
|
-
if (baseUrl) {
|
|
346
|
-
return baseUrl;
|
|
347
|
-
}
|
|
348
|
-
const firstHost = provider.allowedHosts?.[0];
|
|
349
|
-
return firstHost ? `https://${firstHost}` : undefined;
|
|
350
|
-
}
|
|
351
345
|
function getProviderStealthProfile(provider) {
|
|
352
346
|
return provider.stealth ? getStealthProfile(provider.stealth) : undefined;
|
|
353
347
|
}
|
|
@@ -392,7 +386,7 @@ function createStealthChallengeDetection(provider, resolverRuntime, resolverOver
|
|
|
392
386
|
const solve = () => resolver.solve(challenge, solveSignal, recorder ?? undefined);
|
|
393
387
|
return recorder
|
|
394
388
|
? recorder.runSpan("resolver.solve", solve, {
|
|
395
|
-
attributes: { challenge_kind: challenge.kind },
|
|
389
|
+
attributes: resolverSpanAttributes({ challenge_kind: challenge.kind }),
|
|
396
390
|
})
|
|
397
391
|
: solve();
|
|
398
392
|
},
|
|
@@ -849,8 +843,9 @@ function zodDetails(error, redact) {
|
|
|
849
843
|
// (credential_unavailable) usually means a caller credential problem, an
|
|
850
844
|
// internal stateful-routing deadline is APIFuse-owned despite its timeout
|
|
851
845
|
// category, and the built-in upstream failure families keep their upstream
|
|
852
|
-
// attribution even
|
|
853
|
-
//
|
|
846
|
+
// attribution even under an explicit non-upstream category. An unset category
|
|
847
|
+
// needs no override: a registered upstream code already carries its category
|
|
848
|
+
// (SDK_UPSTREAM_ERROR_CODE_REGISTRY), and the source follows from it.
|
|
854
849
|
function publicErrorSource(error, category) {
|
|
855
850
|
if (error instanceof StatefulRoutingDeadlineError)
|
|
856
851
|
return "apifuse";
|
|
@@ -1120,6 +1115,18 @@ function providerObservabilityDetails(error, declaredErrorCode) {
|
|
|
1120
1115
|
...(error.upstreamStatus ? { upstreamStatus: error.upstreamStatus } : {}),
|
|
1121
1116
|
};
|
|
1122
1117
|
}
|
|
1118
|
+
// The category a `ProviderError` (validation errors included) gets when its
|
|
1119
|
+
// author set none, ahead of the branch's own default: an operation-declared
|
|
1120
|
+
// rejection status (409/410/422) → upstream_rejected, then the registered
|
|
1121
|
+
// upstream code's category (UPSTREAM_SCHEMA_ERROR → upstream_schema_drift,
|
|
1122
|
+
// NOT_FOUND → upstream_http, ...). `undefined` leaves the branch default.
|
|
1123
|
+
function derivedErrorCategory(error, declaredStatus) {
|
|
1124
|
+
if (isEmittableErrorStatus(declaredStatus) &&
|
|
1125
|
+
categoryForStatus(declaredStatus) === "upstream_rejected") {
|
|
1126
|
+
return "upstream_rejected";
|
|
1127
|
+
}
|
|
1128
|
+
return registeredUpstreamErrorCategory(providerErrorCode(error));
|
|
1129
|
+
}
|
|
1123
1130
|
function classifiedErrorObservabilityDetails(error, declaredErrorCode) {
|
|
1124
1131
|
const effectiveDeclaration = sdkOwnsErrorResolution(error) ? undefined : declaredErrorCode;
|
|
1125
1132
|
const providerDetails = providerObservabilityDetails(error, effectiveDeclaration);
|
|
@@ -1131,12 +1138,10 @@ function classifiedErrorObservabilityDetails(error, declaredErrorCode) {
|
|
|
1131
1138
|
return {
|
|
1132
1139
|
category: providerCategory
|
|
1133
1140
|
? providerCategory
|
|
1134
|
-
:
|
|
1135
|
-
|
|
1136
|
-
? "upstream_rejected"
|
|
1137
|
-
: isEmittableErrorStatus(declaredStatus) && declaredStatus >= 500
|
|
1141
|
+
: (derivedErrorCategory(error, declaredStatus) ??
|
|
1142
|
+
(isEmittableErrorStatus(declaredStatus) && declaredStatus >= 500
|
|
1138
1143
|
? "provider_error"
|
|
1139
|
-
: "input_validation",
|
|
1144
|
+
: "input_validation")),
|
|
1140
1145
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
1141
1146
|
retryable: isProviderError(error)
|
|
1142
1147
|
? (providerErrorOption(error, "retryable") ?? effectiveDeclaration?.retryable ?? false)
|
|
@@ -1151,18 +1156,12 @@ function classifiedErrorObservabilityDetails(error, declaredErrorCode) {
|
|
|
1151
1156
|
};
|
|
1152
1157
|
}
|
|
1153
1158
|
if (isProviderError(error)) {
|
|
1154
|
-
//
|
|
1155
|
-
//
|
|
1156
|
-
// status (409/410/422) classify as upstream_rejected unless the
|
|
1157
|
-
// author set an explicit category.
|
|
1158
|
-
const declaredStatus = effectiveDeclaration?.status;
|
|
1159
|
-
const rejectionDefault = providerErrorCode(error) === "UPSTREAM_REJECTED" ||
|
|
1160
|
-
(isEmittableErrorStatus(declaredStatus) &&
|
|
1161
|
-
categoryForStatus(declaredStatus) === "upstream_rejected")
|
|
1162
|
-
? "upstream_rejected"
|
|
1163
|
-
: "provider_error";
|
|
1159
|
+
// Retryability does not follow the derived category: it stays
|
|
1160
|
+
// instance ?? declared ?? false below.
|
|
1164
1161
|
return {
|
|
1165
|
-
category: providerErrorOption(error, "category") ??
|
|
1162
|
+
category: providerErrorOption(error, "category") ??
|
|
1163
|
+
derivedErrorCategory(error, effectiveDeclaration?.status) ??
|
|
1164
|
+
"provider_error",
|
|
1166
1165
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
1167
1166
|
retryable: providerErrorOption(error, "retryable") ?? effectiveDeclaration?.retryable ?? false,
|
|
1168
1167
|
};
|
|
@@ -2560,7 +2559,13 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
|
|
|
2560
2559
|
});
|
|
2561
2560
|
}
|
|
2562
2561
|
app.notFound((c) => {
|
|
2563
|
-
|
|
2562
|
+
// An unknown route is not an upstream answer: the explicit category keeps
|
|
2563
|
+
// the registered not_found code from attributing it to an upstream.
|
|
2564
|
+
const error = new ProviderError("Not found", {
|
|
2565
|
+
code: "not_found",
|
|
2566
|
+
category: "provider_error",
|
|
2567
|
+
retryable: false,
|
|
2568
|
+
});
|
|
2564
2569
|
const observabilityDetails = errorObservabilityDetails(error);
|
|
2565
2570
|
return responseWithErrorObservability(c.json(toErrorResponse(error, undefined, observabilityDetails, undefined, errorEnvelopeLocalization({
|
|
2566
2571
|
catalogs: localeCatalogs,
|
|
@@ -2968,6 +2973,16 @@ export async function serve(provider, options = {}) {
|
|
|
2968
2973
|
// One structured line per process boot, before the listener binds: the fleet
|
|
2969
2974
|
// audit (ADR-0011 migration) counts these.
|
|
2970
2975
|
logger(engineModeLogEvent(provider, engineSelection.resolution, !isUnavailableProviderEngine(engineSelection.engine)));
|
|
2976
|
+
// Same contract for the stores: one line per process boot naming the state
|
|
2977
|
+
// and cache backends this pod resolved, so "which Redis is this provider's
|
|
2978
|
+
// state actually on" is a log grep instead of an inference from the
|
|
2979
|
+
// Deployment spec (apifuse#2302).
|
|
2980
|
+
logger(providerStateBackendLogEvent({
|
|
2981
|
+
providerId: provider.id,
|
|
2982
|
+
sdkVersion: SDK_VERSION,
|
|
2983
|
+
injectedState: options.state !== undefined,
|
|
2984
|
+
allowMemoryFallback: options.allowMemoryStateFallback === true,
|
|
2985
|
+
}));
|
|
2971
2986
|
const servers = [];
|
|
2972
2987
|
try {
|
|
2973
2988
|
servers.push(bunRuntime.serve({
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a provider store resolved to at boot.
|
|
3
|
+
*
|
|
4
|
+
* `endpoint` is deliberately host and port only: the URL can carry a password
|
|
5
|
+
* (`redis://:secret@host:6379`), and the diagnostic inventory already treats
|
|
6
|
+
* those credential components — and only those — as sensitive
|
|
7
|
+
* (`collectDiagnosticSensitiveValues`, `urlCredentialComponents(url, {
|
|
8
|
+
* includePathAndQuery: false })`). A URL that does not parse contributes no
|
|
9
|
+
* endpoint at all rather than a redacted-looking fragment of itself.
|
|
10
|
+
*/
|
|
11
|
+
export type ProviderStoreBackendReport = {
|
|
12
|
+
/**
|
|
13
|
+
* `redis` when a URL resolved, `memory` for the in-process fallback,
|
|
14
|
+
* `unsupported` when state has no backend and every call fails closed,
|
|
15
|
+
* `injected` when the host handed the server a store object.
|
|
16
|
+
*/
|
|
17
|
+
readonly backend: "redis" | "memory" | "unsupported" | "injected";
|
|
18
|
+
/** Env name that supplied the Redis URL. Present only for `redis`. */
|
|
19
|
+
readonly envName?: string;
|
|
20
|
+
/**
|
|
21
|
+
* `host:port` of the resolved Redis. Never includes credentials, path or
|
|
22
|
+
* query. Absent when the URL does not parse, or when its query carries an
|
|
23
|
+
* ioredis option that moves the endpoint (`path`, `port`, `host`, `family`)
|
|
24
|
+
* so the authority is not where the client connects — see
|
|
25
|
+
* `state_redis_url_ambiguous_endpoint`.
|
|
26
|
+
*/
|
|
27
|
+
readonly endpoint?: string;
|
|
28
|
+
/** URL scheme, e.g. `redis` or `rediss`. Present only for `redis`. */
|
|
29
|
+
readonly scheme?: string;
|
|
30
|
+
/** True when the URL came from a shared fallback env rather than the store's own. */
|
|
31
|
+
readonly fallback?: boolean;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* One line per process boot naming the stores the provider resolved. Without
|
|
35
|
+
* it, a provider silently riding the cache Redis for its state is
|
|
36
|
+
* indistinguishable from one on a durable instance, from outside the pod and
|
|
37
|
+
* from the pod's own logs (apifuse#2302, the reason apifuse#2144 could only be
|
|
38
|
+
* verified by inference from the Deployment spec).
|
|
39
|
+
*/
|
|
40
|
+
export type ProviderStateBackendLogEvent = {
|
|
41
|
+
/** `warn` when a warning was raised, otherwise `info`. */
|
|
42
|
+
readonly level: "info" | "warn";
|
|
43
|
+
readonly event: "provider_state_backend";
|
|
44
|
+
readonly providerId: string;
|
|
45
|
+
readonly state: ProviderStoreBackendReport;
|
|
46
|
+
readonly cache: ProviderStoreBackendReport;
|
|
47
|
+
readonly sdkVersion: string;
|
|
48
|
+
/**
|
|
49
|
+
* Non-fatal findings, e.g. `state_redis_url_fallback` (the state store is
|
|
50
|
+
* sharing an instance provisioned for something else),
|
|
51
|
+
* `state_redis_url_unparsed`, or `state_redis_url_ambiguous_endpoint` (the
|
|
52
|
+
* URL's query carries an ioredis option that moves the endpoint, so the
|
|
53
|
+
* authority is not reported as the endpoint). Absent when the resolution
|
|
54
|
+
* was unambiguous.
|
|
55
|
+
*/
|
|
56
|
+
readonly warnings?: readonly string[];
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Build the boot event from the same env precedence the stores themselves use
|
|
60
|
+
* (`providerStateRedisResolutionFromEnv` / `providerCacheRedisResolutionFromEnv`),
|
|
61
|
+
* so the line can never describe a backend other than the one in use.
|
|
62
|
+
*/
|
|
63
|
+
export declare function providerStateBackendLogEvent(input: {
|
|
64
|
+
readonly providerId: string;
|
|
65
|
+
readonly sdkVersion: string;
|
|
66
|
+
/** The host passed an explicit state store, so no env resolution happened. */
|
|
67
|
+
readonly injectedState: boolean;
|
|
68
|
+
/** Mirrors `createProviderRuntimeStateFromEnv({ allowMemoryFallback })`. */
|
|
69
|
+
readonly allowMemoryFallback: boolean;
|
|
70
|
+
}): ProviderStateBackendLogEvent;
|