@apifuse/provider-sdk 2.2.0-beta.13 → 2.2.0-beta.15
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 +70 -6
- package/CHANGELOG.md +12 -0
- package/dist/define.js +9 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +25 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/native-egress-policy.d.ts +27 -0
- package/dist/native-egress-policy.js +225 -0
- package/dist/provider.d.ts +3 -3
- package/dist/provider.js +2 -2
- package/dist/runtime/executor.js +17 -2
- package/dist/runtime/http.js +189 -9
- package/dist/runtime/native-network.d.ts +39 -4
- package/dist/runtime/native-network.js +365 -20
- package/dist/runtime/redirects.d.ts +29 -0
- package/dist/runtime/redirects.js +36 -0
- package/dist/runtime/stealth.js +16 -44
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/serve.d.ts +9 -0
- package/dist/server/serve.js +190 -51
- package/dist/server/types.d.ts +3 -0
- package/dist/server/types.js +1 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +9 -1
- package/dist/testing/run.js +32 -13
- package/dist/types.d.ts +23 -2
- package/package.json +1 -1
- package/src/define.ts +11 -0
- package/src/errors.ts +37 -0
- package/src/index.ts +12 -1
- package/src/native-egress-policy.ts +285 -0
- package/src/provider.ts +7 -0
- package/src/runtime/executor.ts +22 -2
- package/src/runtime/http.ts +217 -9
- package/src/runtime/native-network.ts +474 -22
- package/src/runtime/redirects.ts +66 -0
- package/src/runtime/stealth.ts +20 -47
- package/src/server/index.ts +2 -0
- package/src/server/serve.ts +226 -68
- package/src/server/types.ts +1 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +9 -1
- package/src/testing/run.ts +39 -14
- package/src/types.ts +32 -2
package/src/runtime/stealth.ts
CHANGED
|
@@ -50,6 +50,11 @@ import {
|
|
|
50
50
|
shouldRetryProxyTransportAttempt,
|
|
51
51
|
validateUnsafeProxyTransportRetryMethods,
|
|
52
52
|
} from "./proxy-retry-policy.js";
|
|
53
|
+
import {
|
|
54
|
+
evaluateRedirectHop,
|
|
55
|
+
isRedirectStatus,
|
|
56
|
+
resolveRedirectUrl,
|
|
57
|
+
} from "./redirects.js";
|
|
53
58
|
import {
|
|
54
59
|
isSensitiveKey,
|
|
55
60
|
redactSensitiveError,
|
|
@@ -731,16 +736,6 @@ function normalizeMethod(method: HttpMethod | string): StealthMethod {
|
|
|
731
736
|
}
|
|
732
737
|
}
|
|
733
738
|
|
|
734
|
-
function isRedirectStatus(status: number): boolean {
|
|
735
|
-
return [301, 302, 303, 307, 308].includes(status);
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
function nextRedirectMethod(status: number, method: StealthMethod): StealthMethod {
|
|
739
|
-
if (status === 303 && method !== "HEAD") return "GET";
|
|
740
|
-
if ((status === 301 || status === 302) && method === "POST") return "GET";
|
|
741
|
-
return method;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
739
|
function locationHeader(headers: Record<string, string>): string | undefined {
|
|
745
740
|
for (const [name, value] of Object.entries(headers)) {
|
|
746
741
|
if (name.toLowerCase() === "location") return value;
|
|
@@ -1256,7 +1251,7 @@ function createSessionFetcher(
|
|
|
1256
1251
|
const redactedLocation = location ? redactRedirectUrl(location) : undefined;
|
|
1257
1252
|
let nextUrl: string | undefined;
|
|
1258
1253
|
try {
|
|
1259
|
-
nextUrl =
|
|
1254
|
+
nextUrl = resolveRedirectUrl(location, responseUrl);
|
|
1260
1255
|
} catch (error) {
|
|
1261
1256
|
throw redactSensitiveError(error, [...sensitiveValues], location, redactedLocation);
|
|
1262
1257
|
}
|
|
@@ -1297,51 +1292,29 @@ function createSessionFetcher(
|
|
|
1297
1292
|
throw sanitizedError;
|
|
1298
1293
|
}
|
|
1299
1294
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
if (!nextUrl) {
|
|
1311
|
-
return {
|
|
1312
|
-
final: response,
|
|
1313
|
-
hops,
|
|
1314
|
-
reason: "missing_location",
|
|
1315
|
-
cookies: cookieJar.snapshot(),
|
|
1316
|
-
cookieStore: cookieJar.serialize(),
|
|
1317
|
-
};
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
if (hops.length > maxHops) {
|
|
1295
|
+
const decision = evaluateRedirectHop({
|
|
1296
|
+
status: response.status,
|
|
1297
|
+
method,
|
|
1298
|
+
nextUrl,
|
|
1299
|
+
shouldStop,
|
|
1300
|
+
redirectCount: hops.length,
|
|
1301
|
+
maxHops,
|
|
1302
|
+
visitedRequests,
|
|
1303
|
+
});
|
|
1304
|
+
if (decision.kind === "stop") {
|
|
1321
1305
|
return {
|
|
1322
1306
|
final: response,
|
|
1323
1307
|
hops,
|
|
1324
|
-
reason:
|
|
1308
|
+
reason: decision.reason,
|
|
1325
1309
|
cookies: cookieJar.snapshot(),
|
|
1326
1310
|
cookieStore: cookieJar.serialize(),
|
|
1327
1311
|
};
|
|
1328
1312
|
}
|
|
1329
|
-
|
|
1330
|
-
const nextMethod = nextRedirectMethod(response.status, method);
|
|
1331
|
-
if (nextMethod !== method) {
|
|
1313
|
+
if (decision.nextMethod !== method) {
|
|
1332
1314
|
body = undefined;
|
|
1333
1315
|
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
final: response,
|
|
1337
|
-
hops,
|
|
1338
|
-
reason: "loop",
|
|
1339
|
-
cookies: cookieJar.snapshot(),
|
|
1340
|
-
cookieStore: cookieJar.serialize(),
|
|
1341
|
-
};
|
|
1342
|
-
}
|
|
1343
|
-
method = nextMethod;
|
|
1344
|
-
currentUrl = nextUrl;
|
|
1316
|
+
method = decision.nextMethod;
|
|
1317
|
+
currentUrl = decision.nextUrl;
|
|
1345
1318
|
}
|
|
1346
1319
|
|
|
1347
1320
|
if (!response) {
|
package/src/server/index.ts
CHANGED
package/src/server/serve.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
isProviderError,
|
|
10
10
|
isSessionExpiredError,
|
|
11
11
|
isTransportError,
|
|
12
|
+
isValidationError,
|
|
12
13
|
ProviderError,
|
|
13
14
|
} from "../errors.js";
|
|
14
15
|
import {
|
|
@@ -35,6 +36,7 @@ import { createEnvContext } from "../runtime/env.js";
|
|
|
35
36
|
import { executeOperation } from "../runtime/executor.js";
|
|
36
37
|
import { createHttpClient } from "../runtime/http.js";
|
|
37
38
|
import { wrapWithInstrumentation } from "../runtime/instrumentation.js";
|
|
39
|
+
import { createNativeNetworkClient } from "../runtime/native-network.js";
|
|
38
40
|
import { getProviderBaseUrl } from "../runtime/provider.js";
|
|
39
41
|
import {
|
|
40
42
|
PROXY_AUTH_IP_DENIED_CODE,
|
|
@@ -81,6 +83,7 @@ import type {
|
|
|
81
83
|
OperationSseTransport,
|
|
82
84
|
ProviderContext,
|
|
83
85
|
ProviderDefinition,
|
|
86
|
+
ProviderProxyPolicy,
|
|
84
87
|
ProviderRuntimeState,
|
|
85
88
|
ProviderStreamEvent,
|
|
86
89
|
StealthClient,
|
|
@@ -108,6 +111,14 @@ import {
|
|
|
108
111
|
|
|
109
112
|
const DEFAULT_HOST = "0.0.0.0";
|
|
110
113
|
const DEFAULT_PORT = 3000;
|
|
114
|
+
/** Compact SDK-owned error classification emitted separately from the public response body. */
|
|
115
|
+
export const ERROR_OBSERVABILITY_HEADER = "X-ApiFuse-Error-Observability";
|
|
116
|
+
export type ErrorObservabilityDetails = {
|
|
117
|
+
category: ProviderErrorCategory;
|
|
118
|
+
taxonomyVersion: string;
|
|
119
|
+
retryable: boolean;
|
|
120
|
+
upstreamStatus?: number;
|
|
121
|
+
};
|
|
111
122
|
const AUTH_FLOW_LOCALES = ["en", "ko", "ja"] as const;
|
|
112
123
|
const retryResponseMeta = new WeakMap<ProviderContext, HttpRetrySummary>();
|
|
113
124
|
const STATEFUL_INTERNAL_OPERATIONS_ROUTE = "/__apifuse/stateful/operations";
|
|
@@ -278,6 +289,13 @@ function resolveOperationConnectionId(request: OperationRequest): string | undef
|
|
|
278
289
|
return request.connection?.id ?? request.connectionId;
|
|
279
290
|
}
|
|
280
291
|
|
|
292
|
+
function resolveNativeProxyPolicy(provider: ProviderDefinition): ProviderProxyPolicy | undefined {
|
|
293
|
+
if (typeof provider.proxy === "object") return provider.proxy;
|
|
294
|
+
if (provider.proxy === true) return { mode: "optional" };
|
|
295
|
+
if (provider.proxy === false) return { mode: "disabled" };
|
|
296
|
+
return undefined;
|
|
297
|
+
}
|
|
298
|
+
|
|
281
299
|
function createProviderContext(
|
|
282
300
|
provider: ProviderDefinition,
|
|
283
301
|
request: OperationRequest,
|
|
@@ -344,6 +362,17 @@ function createProviderContext(
|
|
|
344
362
|
engine: provider.browser?.engine,
|
|
345
363
|
})
|
|
346
364
|
: createBrowserStub(),
|
|
365
|
+
...(provider.native
|
|
366
|
+
? {
|
|
367
|
+
native: {
|
|
368
|
+
network: createNativeNetworkClient({
|
|
369
|
+
egress: provider.native.network,
|
|
370
|
+
proxyPolicy: resolveNativeProxyPolicy(provider),
|
|
371
|
+
affinityKey: proxyClientOptions.affinityKey,
|
|
372
|
+
}),
|
|
373
|
+
},
|
|
374
|
+
}
|
|
375
|
+
: {}),
|
|
347
376
|
trace: createTraceContext(),
|
|
348
377
|
auth: createAuthStub(),
|
|
349
378
|
stt: options.stt ?? createSttClientFromEnv(provider.stt),
|
|
@@ -446,6 +475,17 @@ function createAuthFlowContext(
|
|
|
446
475
|
? createStealthClient(stealthBaseUrl, stealthProfile.name, stealthClientOptions)
|
|
447
476
|
: createStealthClient(stealthBaseUrl, stealthClientOptions)
|
|
448
477
|
: createStealthStub(),
|
|
478
|
+
...(provider.native
|
|
479
|
+
? {
|
|
480
|
+
native: {
|
|
481
|
+
network: createNativeNetworkClient({
|
|
482
|
+
egress: provider.native.network,
|
|
483
|
+
proxyPolicy: resolveNativeProxyPolicy(provider),
|
|
484
|
+
affinityKey: proxyClientOptions.affinityKey,
|
|
485
|
+
}),
|
|
486
|
+
},
|
|
487
|
+
}
|
|
488
|
+
: {}),
|
|
449
489
|
env: createEnvContext(provider.secrets?.map((secret) => secret.name)),
|
|
450
490
|
credential,
|
|
451
491
|
context: flowContextStore.context,
|
|
@@ -486,6 +526,7 @@ export type ProviderServerLogEvent =
|
|
|
486
526
|
errorCategory?: ProviderErrorCategory;
|
|
487
527
|
taxonomyVersion?: string;
|
|
488
528
|
retryable?: boolean;
|
|
529
|
+
signal?: "unregistered_provider_error_code";
|
|
489
530
|
issues?: Array<{ path: string; code: string; message: string }>;
|
|
490
531
|
})
|
|
491
532
|
| {
|
|
@@ -604,26 +645,28 @@ function zodDetails(error: z.ZodError): Array<{
|
|
|
604
645
|
}
|
|
605
646
|
|
|
606
647
|
function toErrorResponse(error: unknown, requestId?: string): OperationErrorResponse {
|
|
648
|
+
const observability = errorObservabilityDetails(error);
|
|
607
649
|
if (error instanceof StatefulRoutingDeadlineError) {
|
|
608
650
|
return {
|
|
609
651
|
error: {
|
|
610
652
|
code: "STATEFUL_FORWARDING_DEADLINE_EXPIRED",
|
|
611
653
|
message: "Stateful forwarding deadline expired.",
|
|
612
654
|
...(requestId ? { requestId } : {}),
|
|
613
|
-
|
|
655
|
+
retryable: observability.retryable,
|
|
614
656
|
},
|
|
615
657
|
};
|
|
616
658
|
}
|
|
617
659
|
|
|
618
660
|
if (isProviderError(error)) {
|
|
619
|
-
const details =
|
|
661
|
+
const details = error.details;
|
|
620
662
|
return {
|
|
621
663
|
error: {
|
|
622
664
|
code: error.code ?? "provider_error",
|
|
623
665
|
message: publicProviderErrorMessage(error),
|
|
624
666
|
...(requestId ? { requestId } : {}),
|
|
667
|
+
retryable: observability.retryable,
|
|
625
668
|
...(error.fix ? { fix: error.fix } : {}),
|
|
626
|
-
...(details ? { details } : {}),
|
|
669
|
+
...(details !== undefined ? { details } : {}),
|
|
627
670
|
},
|
|
628
671
|
};
|
|
629
672
|
}
|
|
@@ -634,6 +677,7 @@ function toErrorResponse(error: unknown, requestId?: string): OperationErrorResp
|
|
|
634
677
|
code: "invalid_request",
|
|
635
678
|
message: "Invalid request body",
|
|
636
679
|
...(requestId ? { requestId } : {}),
|
|
680
|
+
retryable: observability.retryable,
|
|
637
681
|
details: zodDetails(error),
|
|
638
682
|
},
|
|
639
683
|
};
|
|
@@ -650,6 +694,7 @@ function toErrorResponse(error: unknown, requestId?: string): OperationErrorResp
|
|
|
650
694
|
code: "internal_error",
|
|
651
695
|
message: "Internal error",
|
|
652
696
|
...(requestId ? { requestId } : {}),
|
|
697
|
+
retryable: observability.retryable,
|
|
653
698
|
details: {
|
|
654
699
|
retryable: false,
|
|
655
700
|
category: "internal_error",
|
|
@@ -659,42 +704,12 @@ function toErrorResponse(error: unknown, requestId?: string): OperationErrorResp
|
|
|
659
704
|
};
|
|
660
705
|
}
|
|
661
706
|
|
|
662
|
-
function publicProviderErrorDetails(error: ProviderError): unknown {
|
|
663
|
-
const providerDetails = error.details;
|
|
664
|
-
const observabilityDetails = providerObservabilityDetails(error);
|
|
665
|
-
|
|
666
|
-
if (providerDetails === undefined) {
|
|
667
|
-
return observabilityDetails;
|
|
668
|
-
}
|
|
669
|
-
if (observabilityDetails === undefined) {
|
|
670
|
-
return providerDetails;
|
|
671
|
-
}
|
|
672
|
-
if (isPlainRecord(providerDetails) && isPlainRecord(observabilityDetails)) {
|
|
673
|
-
return { ...providerDetails, ...observabilityDetails };
|
|
674
|
-
}
|
|
675
|
-
return {
|
|
676
|
-
provider: providerDetails,
|
|
677
|
-
observability: observabilityDetails,
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
682
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
683
|
-
}
|
|
684
|
-
|
|
685
707
|
// Accepts `unknown` so the branded guards narrow cleanly from the top: the
|
|
686
708
|
// subtype error classes are structurally compatible with ProviderError, so
|
|
687
709
|
// narrowing from a ProviderError-typed value would collapse the negative branch
|
|
688
710
|
// to `never`. Narrowing from unknown avoids that while still recognizing errors
|
|
689
711
|
// from a duplicate SDK module instance.
|
|
690
|
-
function providerObservabilityDetails(error: unknown):
|
|
691
|
-
| {
|
|
692
|
-
category: ProviderErrorCategory;
|
|
693
|
-
taxonomyVersion: string;
|
|
694
|
-
retryable: boolean;
|
|
695
|
-
upstreamStatus?: number;
|
|
696
|
-
}
|
|
697
|
-
| undefined {
|
|
712
|
+
function providerObservabilityDetails(error: unknown): ErrorObservabilityDetails | undefined {
|
|
698
713
|
// Session-expiry surfaces the credential_expired category + the opt-in
|
|
699
714
|
// retryable signal so Gateway/Credential Service can refresh and re-drive the
|
|
700
715
|
// operation (see design.md §4.3 D3). Without this branch the auth error would
|
|
@@ -751,6 +766,54 @@ function providerObservabilityDetails(error: unknown):
|
|
|
751
766
|
};
|
|
752
767
|
}
|
|
753
768
|
|
|
769
|
+
function errorObservabilityDetails(error: unknown): ErrorObservabilityDetails {
|
|
770
|
+
const providerDetails = providerObservabilityDetails(error);
|
|
771
|
+
if (providerDetails) return providerDetails;
|
|
772
|
+
|
|
773
|
+
if (error instanceof z.ZodError || isValidationError(error)) {
|
|
774
|
+
return {
|
|
775
|
+
category:
|
|
776
|
+
isProviderError(error) && error.options?.category
|
|
777
|
+
? error.options.category
|
|
778
|
+
: "input_validation",
|
|
779
|
+
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
780
|
+
retryable: isProviderError(error) ? (error.options?.retryable ?? false) : false,
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
if (error instanceof StatefulRoutingDeadlineError) {
|
|
785
|
+
return {
|
|
786
|
+
category: "timeout",
|
|
787
|
+
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
788
|
+
retryable: false,
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
if (isProviderError(error)) {
|
|
793
|
+
return {
|
|
794
|
+
category: error.options?.category ?? "provider_error",
|
|
795
|
+
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
796
|
+
retryable: error.options?.retryable ?? false,
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
return {
|
|
801
|
+
category: "internal_error",
|
|
802
|
+
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
803
|
+
retryable: false,
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function responseWithErrorObservability(response: Response, error: unknown): Response {
|
|
808
|
+
const headers = new Headers(response.headers);
|
|
809
|
+
headers.set(ERROR_OBSERVABILITY_HEADER, JSON.stringify(errorObservabilityDetails(error)));
|
|
810
|
+
return new Response(response.body, {
|
|
811
|
+
status: response.status,
|
|
812
|
+
statusText: response.statusText,
|
|
813
|
+
headers,
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
|
|
754
817
|
function publicProviderErrorMessage(error: ProviderError): string {
|
|
755
818
|
if (isTransportError(error)) {
|
|
756
819
|
if (error.code === PROXY_AUTH_IP_DENIED_CODE) {
|
|
@@ -782,11 +845,6 @@ function toStatusCode(error: unknown): 400 | 401 | 404 | 429 | 500 | 502 | 503 |
|
|
|
782
845
|
if (error instanceof StatefulRoutingDeadlineError) {
|
|
783
846
|
return 504;
|
|
784
847
|
}
|
|
785
|
-
|
|
786
|
-
if (isTransportError(error)) {
|
|
787
|
-
return error.code === "transport_timeout" ? 504 : 502;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
848
|
if (isProviderError(error)) {
|
|
791
849
|
switch (error.code) {
|
|
792
850
|
case "AUTH_REQUIRED":
|
|
@@ -812,13 +870,96 @@ function toStatusCode(error: unknown): 400 | 401 | 404 | 429 | 500 | 502 | 503 |
|
|
|
812
870
|
case "STATEFUL_FORWARDING_REPLAY_CACHE_FULL":
|
|
813
871
|
return 503;
|
|
814
872
|
}
|
|
873
|
+
if (isTransportError(error)) {
|
|
874
|
+
return error.code === "transport_timeout" ? 504 : 502;
|
|
875
|
+
}
|
|
876
|
+
if (isValidationError(error)) {
|
|
877
|
+
return error.options?.category === "output_validation" ? 500 : 400;
|
|
878
|
+
}
|
|
815
879
|
|
|
816
|
-
return
|
|
880
|
+
return 500;
|
|
817
881
|
}
|
|
818
882
|
|
|
819
883
|
return 500;
|
|
820
884
|
}
|
|
821
885
|
|
|
886
|
+
// Codes emitted by SDK-owned paths must never be attributed to provider
|
|
887
|
+
// authors by the unregistered-code signal, even when their intentional status
|
|
888
|
+
// is 500. Provider-authored codes not in this registry retain the signal.
|
|
889
|
+
const SDK_OWNED_PROVIDER_ERROR_CODES = new Set([
|
|
890
|
+
"AUTH_PROMPT_UNAVAILABLE",
|
|
891
|
+
"BROWSER_CDP_POOL_REQUIRED",
|
|
892
|
+
"BROWSER_RUNTIME_UNSUPPORTED",
|
|
893
|
+
"STEALTH_RUNTIME_UNSUPPORTED",
|
|
894
|
+
"SSE_EVENT_UNDECLARED",
|
|
895
|
+
"STREAM_EVENT_TOO_LARGE",
|
|
896
|
+
"STREAM_CHUNK_TOO_LARGE",
|
|
897
|
+
"SSE_RESULT_UNSUPPORTED",
|
|
898
|
+
"STREAM_RESULT_UNSUPPORTED",
|
|
899
|
+
"AUTH_FLOW_NOT_CONFIGURED",
|
|
900
|
+
"refresh_not_supported",
|
|
901
|
+
"RUNTIME_UNSUPPORTED",
|
|
902
|
+
"PROVIDER_STATE_UNSUPPORTED",
|
|
903
|
+
"CHOICE_TOKEN_MASTER_SECRET_NOT_CONFIGURED",
|
|
904
|
+
"CHOICE_STATE_PAYLOAD_TOO_LARGE",
|
|
905
|
+
"CHOICE_STATE_UNAVAILABLE",
|
|
906
|
+
"CHOICE_CONTEXT_REQUIRED",
|
|
907
|
+
"unsupported_stealth_cookie_store_version",
|
|
908
|
+
"provider_secret_error",
|
|
909
|
+
"credential_key_error",
|
|
910
|
+
"credential_mode_error",
|
|
911
|
+
"flow_expired",
|
|
912
|
+
"turn_validation_error",
|
|
913
|
+
"context_access_error",
|
|
914
|
+
"UNSUPPORTED_STT_OPTION",
|
|
915
|
+
"INVALID_STT_AUDIO",
|
|
916
|
+
"STT_AUDIO_TOO_LARGE",
|
|
917
|
+
"STT_UPSTREAM_FAILED",
|
|
918
|
+
"INVALID_STT_VERIFICATION_CODE_OPTIONS",
|
|
919
|
+
"NO_CODE_FOUND",
|
|
920
|
+
"AMBIGUOUS_CODE",
|
|
921
|
+
"retry_invalid_policy",
|
|
922
|
+
"retry_unsafe_method",
|
|
923
|
+
"stealth_cookie_store_serialize_failed",
|
|
924
|
+
"response_too_large",
|
|
925
|
+
"transport_stream_unavailable",
|
|
926
|
+
"transport_invalid_method",
|
|
927
|
+
"http_transport_override_unsupported",
|
|
928
|
+
"http_redirect_policy_invalid",
|
|
929
|
+
"http_redirect_stopped",
|
|
930
|
+
"http_redirect_max_hops",
|
|
931
|
+
"http_redirect_missing_location",
|
|
932
|
+
"http_redirect_loop",
|
|
933
|
+
"transport_invalid_url",
|
|
934
|
+
"retry_exhausted",
|
|
935
|
+
"auth_abort_unsafe_data",
|
|
936
|
+
"credentials_auth_missing_credential_keys",
|
|
937
|
+
"credentials_auth_missing_credential",
|
|
938
|
+
"credentials_auth_invalid_login_result",
|
|
939
|
+
"credentials_auth_unknown_challenge",
|
|
940
|
+
"credentials_auth_unknown_pending_challenge",
|
|
941
|
+
"STATEFUL_FORWARDING_NOT_CONFIGURED",
|
|
942
|
+
"STATEFUL_FORWARDING_SIGNATURE_MISSING",
|
|
943
|
+
"STATEFUL_FORWARDING_NONCE_INVALID",
|
|
944
|
+
"STATEFUL_FORWARDING_TIMESTAMP_INVALID",
|
|
945
|
+
"STATEFUL_FORWARDING_SIGNATURE_INVALID",
|
|
946
|
+
"STATEFUL_FORWARDING_REPLAY_DETECTED",
|
|
947
|
+
"STATEFUL_FORWARDING_REPLAY_CACHE_FULL",
|
|
948
|
+
"STATEFUL_FORWARDING_ENVELOPE_INVALID",
|
|
949
|
+
"STATEFUL_FORWARDING_PROVIDER_MISMATCH",
|
|
950
|
+
"STATEFUL_FORWARDING_SOURCE_POD_MISMATCH",
|
|
951
|
+
"STATEFUL_FORWARDING_OWNER_FENCE_INVALID",
|
|
952
|
+
"STATEFUL_FORWARDING_REQUEST_FAILED",
|
|
953
|
+
"STATEFUL_FORWARDING_CONTEXT_MISSING",
|
|
954
|
+
"STATEFUL_FORWARDING_BAD_RESPONSE",
|
|
955
|
+
"STATEFUL_INTERNAL_EXECUTOR_NOT_CONFIGURED",
|
|
956
|
+
"STATEFUL_FILE_FORWARDING_UNSUPPORTED",
|
|
957
|
+
"STATEFUL_CONTROL_PLANE_OPERATION_AMBIGUOUS",
|
|
958
|
+
"STATEFUL_CONTROL_PLANE_REQUEST_FAILED",
|
|
959
|
+
"STATEFUL_CONTROL_PLANE_HTTP_ERROR",
|
|
960
|
+
"STATEFUL_CONTROL_PLANE_INVALID_RESPONSE",
|
|
961
|
+
]);
|
|
962
|
+
|
|
822
963
|
function extractRequestId(raw: unknown): string | undefined {
|
|
823
964
|
if (!raw || typeof raw !== "object") {
|
|
824
965
|
return undefined;
|
|
@@ -847,7 +988,13 @@ function logProviderError(
|
|
|
847
988
|
: "internal_error";
|
|
848
989
|
const errorClass = error instanceof Error ? error.name : typeof error;
|
|
849
990
|
const message = error instanceof Error ? error.message : String(error);
|
|
850
|
-
const details =
|
|
991
|
+
const details = errorObservabilityDetails(error);
|
|
992
|
+
const isUnregisteredProviderErrorCode =
|
|
993
|
+
status === 500 &&
|
|
994
|
+
isProviderError(error) &&
|
|
995
|
+
!isValidationError(error) &&
|
|
996
|
+
typeof error.code === "string" &&
|
|
997
|
+
!SDK_OWNED_PROVIDER_ERROR_CODES.has(error.code);
|
|
851
998
|
const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
|
|
852
999
|
emit({
|
|
853
1000
|
level: status >= 500 ? "error" : "warn",
|
|
@@ -861,15 +1008,12 @@ function logProviderError(
|
|
|
861
1008
|
code,
|
|
862
1009
|
errorClass,
|
|
863
1010
|
message,
|
|
864
|
-
...(
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
taxonomyVersion: details.taxonomyVersion,
|
|
871
|
-
retryable: details.retryable,
|
|
872
|
-
}
|
|
1011
|
+
...(details.upstreamStatus ? { upstreamStatus: details.upstreamStatus } : {}),
|
|
1012
|
+
errorCategory: details.category,
|
|
1013
|
+
taxonomyVersion: details.taxonomyVersion,
|
|
1014
|
+
retryable: details.retryable,
|
|
1015
|
+
...(isUnregisteredProviderErrorCode
|
|
1016
|
+
? { signal: "unregistered_provider_error_code" as const }
|
|
873
1017
|
: {}),
|
|
874
1018
|
...(error instanceof z.ZodError ? { issues: zodDetails(error) } : {}),
|
|
875
1019
|
});
|
|
@@ -1614,17 +1758,10 @@ export function createServerApp(
|
|
|
1614
1758
|
});
|
|
1615
1759
|
}
|
|
1616
1760
|
|
|
1617
|
-
app.notFound((c) =>
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
code: "not_found",
|
|
1622
|
-
message: "Not found",
|
|
1623
|
-
},
|
|
1624
|
-
},
|
|
1625
|
-
404,
|
|
1626
|
-
),
|
|
1627
|
-
);
|
|
1761
|
+
app.notFound((c) => {
|
|
1762
|
+
const error = new ProviderError("Not found", { code: "not_found", retryable: false });
|
|
1763
|
+
return responseWithErrorObservability(c.json(toErrorResponse(error), 404), error);
|
|
1764
|
+
});
|
|
1628
1765
|
|
|
1629
1766
|
app.get("/health", (c) =>
|
|
1630
1767
|
c.json({
|
|
@@ -1786,7 +1923,10 @@ export function createServerApp(
|
|
|
1786
1923
|
status,
|
|
1787
1924
|
finishRequestCost(requestCost),
|
|
1788
1925
|
);
|
|
1789
|
-
return
|
|
1926
|
+
return responseWithErrorObservability(
|
|
1927
|
+
c.json(toErrorResponse(error, requestId), status),
|
|
1928
|
+
error,
|
|
1929
|
+
);
|
|
1790
1930
|
}
|
|
1791
1931
|
});
|
|
1792
1932
|
|
|
@@ -1850,7 +1990,10 @@ export function createServerApp(
|
|
|
1850
1990
|
);
|
|
1851
1991
|
const telemetryHeader = proxyTelemetry.toHeaderValue();
|
|
1852
1992
|
if (telemetryHeader) c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
|
|
1853
|
-
return
|
|
1993
|
+
return responseWithErrorObservability(
|
|
1994
|
+
c.json(toErrorResponse(error, requestId), status),
|
|
1995
|
+
error,
|
|
1996
|
+
);
|
|
1854
1997
|
}
|
|
1855
1998
|
});
|
|
1856
1999
|
|
|
@@ -1887,7 +2030,10 @@ export function createServerApp(
|
|
|
1887
2030
|
status,
|
|
1888
2031
|
finishRequestCost(requestCost),
|
|
1889
2032
|
);
|
|
1890
|
-
return
|
|
2033
|
+
return responseWithErrorObservability(
|
|
2034
|
+
c.json(toErrorResponse(error, requestId), status),
|
|
2035
|
+
error,
|
|
2036
|
+
);
|
|
1891
2037
|
}
|
|
1892
2038
|
});
|
|
1893
2039
|
|
|
@@ -1924,7 +2070,10 @@ export function createServerApp(
|
|
|
1924
2070
|
status,
|
|
1925
2071
|
finishRequestCost(requestCost),
|
|
1926
2072
|
);
|
|
1927
|
-
return
|
|
2073
|
+
return responseWithErrorObservability(
|
|
2074
|
+
c.json(toErrorResponse(error, requestId), status),
|
|
2075
|
+
error,
|
|
2076
|
+
);
|
|
1928
2077
|
}
|
|
1929
2078
|
});
|
|
1930
2079
|
|
|
@@ -1961,7 +2110,10 @@ export function createServerApp(
|
|
|
1961
2110
|
status,
|
|
1962
2111
|
finishRequestCost(requestCost),
|
|
1963
2112
|
);
|
|
1964
|
-
return
|
|
2113
|
+
return responseWithErrorObservability(
|
|
2114
|
+
c.json(toErrorResponse(error, requestId), status),
|
|
2115
|
+
error,
|
|
2116
|
+
);
|
|
1965
2117
|
}
|
|
1966
2118
|
});
|
|
1967
2119
|
|
|
@@ -1998,7 +2150,10 @@ export function createServerApp(
|
|
|
1998
2150
|
status,
|
|
1999
2151
|
finishRequestCost(requestCost),
|
|
2000
2152
|
);
|
|
2001
|
-
return
|
|
2153
|
+
return responseWithErrorObservability(
|
|
2154
|
+
c.json(toErrorResponse(error, requestId), status),
|
|
2155
|
+
error,
|
|
2156
|
+
);
|
|
2002
2157
|
}
|
|
2003
2158
|
});
|
|
2004
2159
|
|
|
@@ -2035,7 +2190,10 @@ export function createServerApp(
|
|
|
2035
2190
|
status,
|
|
2036
2191
|
finishRequestCost(requestCost),
|
|
2037
2192
|
);
|
|
2038
|
-
return
|
|
2193
|
+
return responseWithErrorObservability(
|
|
2194
|
+
c.json(toErrorResponse(error, requestId), status),
|
|
2195
|
+
error,
|
|
2196
|
+
);
|
|
2039
2197
|
}
|
|
2040
2198
|
});
|
|
2041
2199
|
|
package/src/server/types.ts
CHANGED
|
@@ -35,6 +35,14 @@ type FetchTransport = (url: string | URL | Request, init?: RequestInit) => Promi
|
|
|
35
35
|
|
|
36
36
|
const MAX_FORWARDED_HEADERS = 32;
|
|
37
37
|
const MAX_FORWARDED_HEADER_BYTES = 8 * 1024;
|
|
38
|
+
// Inbound compatibility boundary for rolling deploys: older owner pods omit
|
|
39
|
+
// top-level retryable. Emitted responses remain strict via
|
|
40
|
+
// OperationErrorResponseSchema.
|
|
41
|
+
const ForwardedOperationErrorResponseSchema = OperationErrorResponseSchema.extend({
|
|
42
|
+
error: OperationErrorResponseSchema.shape.error.extend({
|
|
43
|
+
retryable: z.boolean().optional().default(false),
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
38
46
|
const SENSITIVE_HEADER_NAMES = new Set([
|
|
39
47
|
"authorization",
|
|
40
48
|
"cookie",
|
|
@@ -253,7 +261,7 @@ async function parseForwardedResponse(response: Response): Promise<StatefulOpera
|
|
|
253
261
|
return { output: success.data.data };
|
|
254
262
|
}
|
|
255
263
|
|
|
256
|
-
const error =
|
|
264
|
+
const error = ForwardedOperationErrorResponseSchema.safeParse(body);
|
|
257
265
|
if (error.success) {
|
|
258
266
|
throw new StatefulOwnerForwardingError({
|
|
259
267
|
code: error.data.error.code,
|