@apifuse/provider-sdk 2.2.0-beta.15 → 2.2.0-beta.17
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 +92 -13
- package/CHANGELOG.md +10 -0
- package/dist/config/loader.d.ts +19 -0
- package/dist/config/loader.js +59 -28
- package/dist/define.js +20 -1
- package/dist/error-resolution.d.ts +2 -0
- package/dist/error-resolution.js +90 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/native-egress-policy.d.ts +1 -0
- package/dist/native-egress-policy.js +34 -21
- package/dist/native-ipv4.d.ts +22 -0
- package/dist/native-ipv4.js +98 -0
- package/dist/provider.d.ts +2 -2
- package/dist/provider.js +1 -1
- package/dist/runtime/native-network.d.ts +43 -7
- package/dist/runtime/native-network.js +567 -110
- package/dist/runtime/proxy-nodemaven.d.ts +7 -0
- package/dist/runtime/proxy-nodemaven.js +5 -5
- package/dist/server/serve.js +76 -100
- package/dist/types.d.ts +9 -2
- package/dist/types.js +1 -0
- package/package.json +1 -1
- package/src/config/loader.ts +110 -18
- package/src/define.ts +33 -0
- package/src/error-resolution.ts +91 -0
- package/src/index.ts +6 -0
- package/src/native-egress-policy.ts +39 -33
- package/src/native-ipv4.ts +118 -0
- package/src/provider.ts +6 -0
- package/src/runtime/native-network.ts +734 -131
- package/src/runtime/proxy-nodemaven.ts +13 -5
- package/src/server/serve.ts +118 -98
- package/src/types.ts +11 -2
|
@@ -8,6 +8,12 @@ export const NODEMAVEN_FILTER_ENV = "APIFUSE__PROXY__NODEMAVEN_FILTER";
|
|
|
8
8
|
|
|
9
9
|
export const NODEMAVEN_GATEWAY_HOST = "gate.nodemaven.com";
|
|
10
10
|
|
|
11
|
+
export type NodemavenCredentials = {
|
|
12
|
+
readonly username: string;
|
|
13
|
+
readonly password: string;
|
|
14
|
+
readonly filter?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
11
17
|
/** Both schemes tunnel bytes end-to-end, preserving the client TLS handshake. */
|
|
12
18
|
export type ProxyProtocol = "http" | "socks5";
|
|
13
19
|
|
|
@@ -44,8 +50,8 @@ function readNodemavenPassword(): string | undefined {
|
|
|
44
50
|
return process.env[NODEMAVEN_PASSWORD_ENV]?.trim() || undefined;
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
function resolveNodemavenFilter(): string {
|
|
48
|
-
const raw =
|
|
53
|
+
function resolveNodemavenFilter(value: string | undefined): string {
|
|
54
|
+
const raw = value?.trim().toLowerCase();
|
|
49
55
|
if (!raw) return DEFAULT_NODEMAVEN_FILTER;
|
|
50
56
|
if (!NODEMAVEN_FILTERS.has(raw)) {
|
|
51
57
|
throw new Error(`${NODEMAVEN_FILTER_ENV} must be "medium" or "high"`);
|
|
@@ -133,6 +139,8 @@ function selectPort(protocol: ProxyProtocol, sid: string, poolIndex: number): nu
|
|
|
133
139
|
|
|
134
140
|
export type NodemavenSynthesisInput = {
|
|
135
141
|
policy: ProviderProxyPolicy;
|
|
142
|
+
/** Explicit credentials; synthesis never reads process-global state. */
|
|
143
|
+
credentials: NodemavenCredentials;
|
|
136
144
|
affinityKey: string | undefined;
|
|
137
145
|
protocol: ProxyProtocol;
|
|
138
146
|
poolIndex: number;
|
|
@@ -159,15 +167,15 @@ export type NodemavenSynthesis = {
|
|
|
159
167
|
* There is no allocation API — geo/session are encoded in the username.
|
|
160
168
|
*/
|
|
161
169
|
export function synthesizeNodemavenProxy(input: NodemavenSynthesisInput): NodemavenSynthesis {
|
|
162
|
-
const username =
|
|
163
|
-
const password =
|
|
170
|
+
const username = input.credentials.username.trim();
|
|
171
|
+
const password = input.credentials.password.trim();
|
|
164
172
|
if (!username || !password) {
|
|
165
173
|
throw new Error(
|
|
166
174
|
`NodeMaven credentials missing: set ${NODEMAVEN_USERNAME_ENV} and ${NODEMAVEN_PASSWORD_ENV}.`,
|
|
167
175
|
);
|
|
168
176
|
}
|
|
169
177
|
|
|
170
|
-
const filter = resolveNodemavenFilter();
|
|
178
|
+
const filter = resolveNodemavenFilter(input.credentials.filter);
|
|
171
179
|
const sid = deriveSid(input.policy, input.affinityKey, input.poolIndex, input.refreshEpoch);
|
|
172
180
|
const port = selectPort(input.protocol, sid, input.poolIndex);
|
|
173
181
|
const lifetimeMinutes = nodemavenLifetimeMinutes(input.policy);
|
package/src/server/serve.ts
CHANGED
|
@@ -4,6 +4,10 @@ import { join } from "node:path";
|
|
|
4
4
|
import { Hono } from "hono";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { AuthAbortError, createAuthFlowHelpers } from "../auth.js";
|
|
7
|
+
import {
|
|
8
|
+
SDK_OWNED_PROVIDER_ERROR_CODES,
|
|
9
|
+
SDK_RUNTIME_OWNED_ERROR_CODES,
|
|
10
|
+
} from "../error-resolution.js";
|
|
7
11
|
import {
|
|
8
12
|
AuthError,
|
|
9
13
|
isProviderError,
|
|
@@ -36,7 +40,10 @@ import { createEnvContext } from "../runtime/env.js";
|
|
|
36
40
|
import { executeOperation } from "../runtime/executor.js";
|
|
37
41
|
import { createHttpClient } from "../runtime/http.js";
|
|
38
42
|
import { wrapWithInstrumentation } from "../runtime/instrumentation.js";
|
|
39
|
-
import {
|
|
43
|
+
import {
|
|
44
|
+
createEnvVendorCredentialResolver,
|
|
45
|
+
createNativeNetworkClient,
|
|
46
|
+
} from "../runtime/native-network.js";
|
|
40
47
|
import { getProviderBaseUrl } from "../runtime/provider.js";
|
|
41
48
|
import {
|
|
42
49
|
PROXY_AUTH_IP_DENIED_CODE,
|
|
@@ -79,8 +86,10 @@ import type {
|
|
|
79
86
|
FlowContextStore,
|
|
80
87
|
HttpRetrySummary,
|
|
81
88
|
OperationDefinition,
|
|
89
|
+
OperationErrorCode,
|
|
82
90
|
OperationHttpStreamTransport,
|
|
83
91
|
OperationSseTransport,
|
|
92
|
+
ProviderErrorStatus,
|
|
84
93
|
ProviderContext,
|
|
85
94
|
ProviderDefinition,
|
|
86
95
|
ProviderProxyPolicy,
|
|
@@ -89,6 +98,7 @@ import type {
|
|
|
89
98
|
StealthClient,
|
|
90
99
|
SttContext,
|
|
91
100
|
} from "../types.js";
|
|
101
|
+
import { VALID_OPERATION_ERROR_STATUSES } from "../types.js";
|
|
92
102
|
import {
|
|
93
103
|
createSelfTestApp,
|
|
94
104
|
createSelfTestAuthFlowInvoke,
|
|
@@ -369,6 +379,7 @@ function createProviderContext(
|
|
|
369
379
|
egress: provider.native.network,
|
|
370
380
|
proxyPolicy: resolveNativeProxyPolicy(provider),
|
|
371
381
|
affinityKey: proxyClientOptions.affinityKey,
|
|
382
|
+
credentials: createEnvVendorCredentialResolver(env),
|
|
372
383
|
}),
|
|
373
384
|
},
|
|
374
385
|
}
|
|
@@ -482,6 +493,9 @@ function createAuthFlowContext(
|
|
|
482
493
|
egress: provider.native.network,
|
|
483
494
|
proxyPolicy: resolveNativeProxyPolicy(provider),
|
|
484
495
|
affinityKey: proxyClientOptions.affinityKey,
|
|
496
|
+
credentials: createEnvVendorCredentialResolver(
|
|
497
|
+
createEnvContext(provider.secrets?.map((secret) => secret.name)),
|
|
498
|
+
),
|
|
485
499
|
}),
|
|
486
500
|
},
|
|
487
501
|
}
|
|
@@ -644,8 +658,12 @@ function zodDetails(error: z.ZodError): Array<{
|
|
|
644
658
|
}));
|
|
645
659
|
}
|
|
646
660
|
|
|
647
|
-
function toErrorResponse(
|
|
648
|
-
|
|
661
|
+
function toErrorResponse(
|
|
662
|
+
error: unknown,
|
|
663
|
+
requestId?: string,
|
|
664
|
+
declaredErrorCode?: OperationErrorCode,
|
|
665
|
+
): OperationErrorResponse {
|
|
666
|
+
const observability = errorObservabilityDetails(error, declaredErrorCode);
|
|
649
667
|
if (error instanceof StatefulRoutingDeadlineError) {
|
|
650
668
|
return {
|
|
651
669
|
error: {
|
|
@@ -709,7 +727,13 @@ function toErrorResponse(error: unknown, requestId?: string): OperationErrorResp
|
|
|
709
727
|
// narrowing from a ProviderError-typed value would collapse the negative branch
|
|
710
728
|
// to `never`. Narrowing from unknown avoids that while still recognizing errors
|
|
711
729
|
// from a duplicate SDK module instance.
|
|
712
|
-
function providerObservabilityDetails(
|
|
730
|
+
function providerObservabilityDetails(
|
|
731
|
+
error: unknown,
|
|
732
|
+
declaredErrorCode?: OperationErrorCode,
|
|
733
|
+
): ErrorObservabilityDetails | undefined {
|
|
734
|
+
const declaredRetryable = sdkOwnsErrorResolution(error)
|
|
735
|
+
? undefined
|
|
736
|
+
: declaredErrorCode?.retryable;
|
|
713
737
|
// Session-expiry surfaces the credential_expired category + the opt-in
|
|
714
738
|
// retryable signal so Gateway/Credential Service can refresh and re-drive the
|
|
715
739
|
// operation (see design.md §4.3 D3). Without this branch the auth error would
|
|
@@ -719,7 +743,7 @@ function providerObservabilityDetails(error: unknown): ErrorObservabilityDetails
|
|
|
719
743
|
return {
|
|
720
744
|
category: error.options?.category ?? "credential_expired",
|
|
721
745
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
722
|
-
retryable: error.options?.retryable ?? false,
|
|
746
|
+
retryable: error.options?.retryable ?? declaredRetryable ?? false,
|
|
723
747
|
};
|
|
724
748
|
}
|
|
725
749
|
// Missing-secret errors carry the canonical credential_unavailable category
|
|
@@ -731,7 +755,7 @@ function providerObservabilityDetails(error: unknown): ErrorObservabilityDetails
|
|
|
731
755
|
return {
|
|
732
756
|
category: error.options?.category ?? "credential_unavailable",
|
|
733
757
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
734
|
-
retryable: error.options?.retryable ?? false,
|
|
758
|
+
retryable: error.options?.retryable ?? declaredRetryable ?? false,
|
|
735
759
|
};
|
|
736
760
|
}
|
|
737
761
|
if (!isTransportError(error)) {
|
|
@@ -766,18 +790,27 @@ function providerObservabilityDetails(error: unknown): ErrorObservabilityDetails
|
|
|
766
790
|
};
|
|
767
791
|
}
|
|
768
792
|
|
|
769
|
-
function errorObservabilityDetails(
|
|
770
|
-
|
|
793
|
+
function errorObservabilityDetails(
|
|
794
|
+
error: unknown,
|
|
795
|
+
declaredErrorCode?: OperationErrorCode,
|
|
796
|
+
): ErrorObservabilityDetails {
|
|
797
|
+
const effectiveDeclaration = sdkOwnsErrorResolution(error) ? undefined : declaredErrorCode;
|
|
798
|
+
const providerDetails = providerObservabilityDetails(error, effectiveDeclaration);
|
|
771
799
|
if (providerDetails) return providerDetails;
|
|
772
800
|
|
|
773
801
|
if (error instanceof z.ZodError || isValidationError(error)) {
|
|
802
|
+
const declaredStatus = effectiveDeclaration?.status;
|
|
774
803
|
return {
|
|
775
804
|
category:
|
|
776
805
|
isProviderError(error) && error.options?.category
|
|
777
806
|
? error.options.category
|
|
778
|
-
:
|
|
807
|
+
: isEmittableErrorStatus(declaredStatus) && declaredStatus >= 500
|
|
808
|
+
? "provider_error"
|
|
809
|
+
: "input_validation",
|
|
779
810
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
780
|
-
retryable: isProviderError(error)
|
|
811
|
+
retryable: isProviderError(error)
|
|
812
|
+
? (error.options?.retryable ?? effectiveDeclaration?.retryable ?? false)
|
|
813
|
+
: false,
|
|
781
814
|
};
|
|
782
815
|
}
|
|
783
816
|
|
|
@@ -793,7 +826,7 @@ function errorObservabilityDetails(error: unknown): ErrorObservabilityDetails {
|
|
|
793
826
|
return {
|
|
794
827
|
category: error.options?.category ?? "provider_error",
|
|
795
828
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
796
|
-
retryable: error.options?.retryable ?? false,
|
|
829
|
+
retryable: error.options?.retryable ?? effectiveDeclaration?.retryable ?? false,
|
|
797
830
|
};
|
|
798
831
|
}
|
|
799
832
|
|
|
@@ -804,9 +837,16 @@ function errorObservabilityDetails(error: unknown): ErrorObservabilityDetails {
|
|
|
804
837
|
};
|
|
805
838
|
}
|
|
806
839
|
|
|
807
|
-
function responseWithErrorObservability(
|
|
840
|
+
function responseWithErrorObservability(
|
|
841
|
+
response: Response,
|
|
842
|
+
error: unknown,
|
|
843
|
+
declaredErrorCode?: OperationErrorCode,
|
|
844
|
+
): Response {
|
|
808
845
|
const headers = new Headers(response.headers);
|
|
809
|
-
headers.set(
|
|
846
|
+
headers.set(
|
|
847
|
+
ERROR_OBSERVABILITY_HEADER,
|
|
848
|
+
JSON.stringify(errorObservabilityDetails(error, declaredErrorCode)),
|
|
849
|
+
);
|
|
810
850
|
return new Response(response.body, {
|
|
811
851
|
status: response.status,
|
|
812
852
|
statusText: response.statusText,
|
|
@@ -838,7 +878,14 @@ function publicProviderErrorMessage(error: ProviderError): string {
|
|
|
838
878
|
return error.message;
|
|
839
879
|
}
|
|
840
880
|
|
|
841
|
-
function
|
|
881
|
+
function isEmittableErrorStatus(value: unknown): value is ProviderErrorStatus {
|
|
882
|
+
return (
|
|
883
|
+
typeof value === "number" &&
|
|
884
|
+
VALID_OPERATION_ERROR_STATUSES.some((status) => status === value)
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function toStatusCode(error: unknown, declaredErrorCode?: OperationErrorCode): ProviderErrorStatus {
|
|
842
889
|
if (error instanceof z.ZodError) {
|
|
843
890
|
return 400;
|
|
844
891
|
}
|
|
@@ -846,6 +893,12 @@ function toStatusCode(error: unknown): 400 | 401 | 404 | 429 | 500 | 502 | 503 |
|
|
|
846
893
|
return 504;
|
|
847
894
|
}
|
|
848
895
|
if (isProviderError(error)) {
|
|
896
|
+
if (
|
|
897
|
+
!sdkOwnsErrorResolution(error) &&
|
|
898
|
+
isEmittableErrorStatus(declaredErrorCode?.status)
|
|
899
|
+
) {
|
|
900
|
+
return declaredErrorCode.status;
|
|
901
|
+
}
|
|
849
902
|
switch (error.code) {
|
|
850
903
|
case "AUTH_REQUIRED":
|
|
851
904
|
case "reauth_required":
|
|
@@ -883,82 +936,39 @@ function toStatusCode(error: unknown): 400 | 401 | 404 | 429 | 500 | 502 | 503 |
|
|
|
883
936
|
return 500;
|
|
884
937
|
}
|
|
885
938
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
"
|
|
917
|
-
|
|
918
|
-
|
|
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
|
-
]);
|
|
939
|
+
function sdkOwnsErrorResolution(error: unknown): boolean {
|
|
940
|
+
if (isSessionExpiredError(error)) return true;
|
|
941
|
+
if (isTransportError(error)) return true;
|
|
942
|
+
if (error instanceof z.ZodError) return true;
|
|
943
|
+
if (error instanceof StatefulRoutingDeadlineError) return true;
|
|
944
|
+
return (
|
|
945
|
+
isProviderError(error) &&
|
|
946
|
+
typeof error.code === "string" &&
|
|
947
|
+
SDK_RUNTIME_OWNED_ERROR_CODES.has(error.code)
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
type OperationErrorCodeLookup = ReadonlyMap<string, ReadonlyMap<string, OperationErrorCode>>;
|
|
952
|
+
|
|
953
|
+
function buildOperationErrorCodeLookup(provider: ProviderDefinition): OperationErrorCodeLookup {
|
|
954
|
+
return new Map(
|
|
955
|
+
Object.entries(provider.operations).flatMap(([operationId, operation]) => {
|
|
956
|
+
const errorCodes = operation.docs?.errorCodes;
|
|
957
|
+
return errorCodes?.length
|
|
958
|
+
? [[operationId, new Map(errorCodes.map((entry) => [entry.code, entry]))] as const]
|
|
959
|
+
: [];
|
|
960
|
+
}),
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
function declaredErrorCodeFor(
|
|
965
|
+
error: unknown,
|
|
966
|
+
operationId: string | undefined,
|
|
967
|
+
lookup: OperationErrorCodeLookup,
|
|
968
|
+
): OperationErrorCode | undefined {
|
|
969
|
+
if (!operationId || !isProviderError(error) || typeof error.code !== "string") return undefined;
|
|
970
|
+
return lookup.get(operationId)?.get(error.code);
|
|
971
|
+
}
|
|
962
972
|
|
|
963
973
|
function extractRequestId(raw: unknown): string | undefined {
|
|
964
974
|
if (!raw || typeof raw !== "object") {
|
|
@@ -978,6 +988,7 @@ function logProviderError(
|
|
|
978
988
|
error: unknown,
|
|
979
989
|
status: number,
|
|
980
990
|
cost: ProviderRequestCost,
|
|
991
|
+
declaredErrorCode?: OperationErrorCode,
|
|
981
992
|
): void {
|
|
982
993
|
const code = isProviderError(error)
|
|
983
994
|
? (error.code ?? "provider_error")
|
|
@@ -988,13 +999,14 @@ function logProviderError(
|
|
|
988
999
|
: "internal_error";
|
|
989
1000
|
const errorClass = error instanceof Error ? error.name : typeof error;
|
|
990
1001
|
const message = error instanceof Error ? error.message : String(error);
|
|
991
|
-
const details = errorObservabilityDetails(error);
|
|
1002
|
+
const details = errorObservabilityDetails(error, declaredErrorCode);
|
|
992
1003
|
const isUnregisteredProviderErrorCode =
|
|
993
1004
|
status === 500 &&
|
|
994
1005
|
isProviderError(error) &&
|
|
995
1006
|
!isValidationError(error) &&
|
|
996
1007
|
typeof error.code === "string" &&
|
|
997
|
-
!SDK_OWNED_PROVIDER_ERROR_CODES.has(error.code)
|
|
1008
|
+
!SDK_OWNED_PROVIDER_ERROR_CODES.has(error.code) &&
|
|
1009
|
+
declaredErrorCode === undefined;
|
|
998
1010
|
const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
|
|
999
1011
|
emit({
|
|
1000
1012
|
level: status >= 500 ? "error" : "warn",
|
|
@@ -1729,6 +1741,7 @@ export function createServerApp(
|
|
|
1729
1741
|
validateStatefulServerConfig(options);
|
|
1730
1742
|
const app = new Hono();
|
|
1731
1743
|
const logger = options.logger ?? defaultProviderServerLogger;
|
|
1744
|
+
const operationErrorCodes = buildOperationErrorCodeLookup(provider);
|
|
1732
1745
|
const statefulForwardingReplayCache = new StatefulForwardingReplayCache(
|
|
1733
1746
|
options.statefulForwarding?.replayCacheMaxEntries ??
|
|
1734
1747
|
DEFAULT_STATEFUL_FORWARDING_REPLAY_CACHE_MAX_ENTRIES,
|
|
@@ -1774,6 +1787,7 @@ export function createServerApp(
|
|
|
1774
1787
|
app.post(STATEFUL_INTERNAL_OPERATIONS_ROUTE, async (c) => {
|
|
1775
1788
|
let rawBodyText = "";
|
|
1776
1789
|
let rawBody: unknown;
|
|
1790
|
+
let operationId: string | undefined;
|
|
1777
1791
|
const operation = "stateful-internal";
|
|
1778
1792
|
const requestCost = startRequestCost();
|
|
1779
1793
|
try {
|
|
@@ -1884,7 +1898,7 @@ export function createServerApp(
|
|
|
1884
1898
|
});
|
|
1885
1899
|
}
|
|
1886
1900
|
const request = operationRequestFromForwardingEnvelope(envelope);
|
|
1887
|
-
|
|
1901
|
+
operationId = envelope.operationId;
|
|
1888
1902
|
const ctx = createProviderContext(provider, request, operationId, options, state);
|
|
1889
1903
|
if (deadlineAtMs !== undefined && deadlineAtMs <= Date.now()) {
|
|
1890
1904
|
throw new StatefulRoutingDeadlineError(envelope.requestId, envelope.deadlineAt as string);
|
|
@@ -1908,7 +1922,8 @@ export function createServerApp(
|
|
|
1908
1922
|
);
|
|
1909
1923
|
return c.json({ data: output });
|
|
1910
1924
|
} catch (error) {
|
|
1911
|
-
const
|
|
1925
|
+
const declaredErrorCode = declaredErrorCodeFor(error, operationId, operationErrorCodes);
|
|
1926
|
+
const status = toStatusCode(error, declaredErrorCode);
|
|
1912
1927
|
if (isProviderError(error) && error.code === "STATEFUL_FORWARDING_REPLAY_CACHE_FULL") {
|
|
1913
1928
|
c.header("Retry-After", String(STATEFUL_FORWARDING_REPLAY_RETRY_AFTER_SECONDS));
|
|
1914
1929
|
}
|
|
@@ -1917,15 +1932,17 @@ export function createServerApp(
|
|
|
1917
1932
|
logger,
|
|
1918
1933
|
provider,
|
|
1919
1934
|
"operation",
|
|
1920
|
-
operation,
|
|
1935
|
+
operationId || operation,
|
|
1921
1936
|
requestId,
|
|
1922
1937
|
error,
|
|
1923
1938
|
status,
|
|
1924
1939
|
finishRequestCost(requestCost),
|
|
1940
|
+
declaredErrorCode,
|
|
1925
1941
|
);
|
|
1926
1942
|
return responseWithErrorObservability(
|
|
1927
|
-
c.json(toErrorResponse(error, requestId), status),
|
|
1943
|
+
c.json(toErrorResponse(error, requestId, declaredErrorCode), status),
|
|
1928
1944
|
error,
|
|
1945
|
+
declaredErrorCode,
|
|
1929
1946
|
);
|
|
1930
1947
|
}
|
|
1931
1948
|
});
|
|
@@ -1976,7 +1993,8 @@ export function createServerApp(
|
|
|
1976
1993
|
);
|
|
1977
1994
|
return c.json(response);
|
|
1978
1995
|
} catch (error) {
|
|
1979
|
-
const
|
|
1996
|
+
const declaredErrorCode = declaredErrorCodeFor(error, operation, operationErrorCodes);
|
|
1997
|
+
const status = toStatusCode(error, declaredErrorCode);
|
|
1980
1998
|
const requestId = extractRequestId(rawBody);
|
|
1981
1999
|
logProviderError(
|
|
1982
2000
|
logger,
|
|
@@ -1987,12 +2005,14 @@ export function createServerApp(
|
|
|
1987
2005
|
error,
|
|
1988
2006
|
status,
|
|
1989
2007
|
finishRequestCost(requestCost),
|
|
2008
|
+
declaredErrorCode,
|
|
1990
2009
|
);
|
|
1991
2010
|
const telemetryHeader = proxyTelemetry.toHeaderValue();
|
|
1992
2011
|
if (telemetryHeader) c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
|
|
1993
2012
|
return responseWithErrorObservability(
|
|
1994
|
-
c.json(toErrorResponse(error, requestId), status),
|
|
2013
|
+
c.json(toErrorResponse(error, requestId, declaredErrorCode), status),
|
|
1995
2014
|
error,
|
|
2015
|
+
declaredErrorCode,
|
|
1996
2016
|
);
|
|
1997
2017
|
}
|
|
1998
2018
|
});
|
package/src/types.ts
CHANGED
|
@@ -719,9 +719,13 @@ export interface HealthMonitorProbeOverride {
|
|
|
719
719
|
degradedThresholdMs?: number;
|
|
720
720
|
}
|
|
721
721
|
|
|
722
|
+
export const VALID_OPERATION_ERROR_STATUSES = [400, 401, 404, 429, 500, 502, 503, 504] as const;
|
|
723
|
+
|
|
724
|
+
export type ProviderErrorStatus = (typeof VALID_OPERATION_ERROR_STATUSES)[number];
|
|
725
|
+
|
|
722
726
|
export interface OperationErrorCode {
|
|
723
727
|
code: string;
|
|
724
|
-
status?:
|
|
728
|
+
status?: ProviderErrorStatus;
|
|
725
729
|
description: string;
|
|
726
730
|
retryable?: boolean;
|
|
727
731
|
}
|
|
@@ -1314,6 +1318,10 @@ export interface NativeTcpEgressRule {
|
|
|
1314
1318
|
/**
|
|
1315
1319
|
* Bounded native TCP egress discovered through a declared bootstrap endpoint.
|
|
1316
1320
|
* Host suffixes are exact DNS suffixes, not wildcard patterns.
|
|
1321
|
+
* Dynamic rules must declare at least one target host selector through
|
|
1322
|
+
* targetHostSuffixes and/or targetIpv4Cidrs. IPv4-literal grant targets match
|
|
1323
|
+
* only targetIpv4Cidrs, while DNS-name targets match only targetHostSuffixes.
|
|
1324
|
+
* CIDRs are exact IPv4 networks in a.b.c.d/nn form.
|
|
1317
1325
|
*
|
|
1318
1326
|
* Dynamic rules are ordered. The first rule whose source, target, port, and TLS
|
|
1319
1327
|
* selectors match exclusively owns the grant; its ttlMs and maxGrants bounds
|
|
@@ -1326,7 +1334,8 @@ export interface NativeTcpDynamicEgressRule {
|
|
|
1326
1334
|
readonly sourceHostSuffixes?: readonly string[];
|
|
1327
1335
|
readonly sourcePorts?: readonly number[];
|
|
1328
1336
|
readonly sourcePortRanges?: readonly NativeTcpPortRange[];
|
|
1329
|
-
readonly targetHostSuffixes
|
|
1337
|
+
readonly targetHostSuffixes?: readonly string[];
|
|
1338
|
+
readonly targetIpv4Cidrs?: readonly string[];
|
|
1330
1339
|
readonly targetPorts?: readonly number[];
|
|
1331
1340
|
readonly targetPortRanges?: readonly NativeTcpPortRange[];
|
|
1332
1341
|
readonly tls: NativeTcpTlsMode;
|