@apifuse/provider-sdk 2.2.0-beta.15 → 2.2.0-beta.16
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 +52 -13
- package/CHANGELOG.md +5 -0
- 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 +1 -1
- package/dist/provider.d.ts +1 -1
- package/dist/server/serve.js +73 -99
- package/dist/types.d.ts +3 -1
- package/dist/types.js +1 -0
- package/package.json +1 -1
- package/src/define.ts +33 -0
- package/src/error-resolution.ts +91 -0
- package/src/index.ts +1 -0
- package/src/provider.ts +1 -0
- package/src/server/serve.ts +110 -97
- package/src/types.ts +5 -1
package/AUTHORING.md
CHANGED
|
@@ -411,12 +411,14 @@ Provider-server failures use a stable public envelope:
|
|
|
411
411
|
|
|
412
412
|
`retryable` is always present on responses emitted by the current SDK. Set
|
|
413
413
|
`retryable` in the `ProviderError` options when the provider knows the answer;
|
|
414
|
-
an explicit `true` or `false` wins over
|
|
415
|
-
SDK
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
414
|
+
an explicit `true` or `false` wins over the matching operation declaration and
|
|
415
|
+
SDK derivation. When it is omitted, `operations.<id>.docs.errorCodes[].retryable`
|
|
416
|
+
is used for a matching provider-owned code, followed by SDK derivation (which
|
|
417
|
+
defaults ordinary `ProviderError` values to `false`). During stateful rolling
|
|
418
|
+
upgrades, the forwarding client also accepts an older owner response that omits
|
|
419
|
+
`retryable` and treats it as `false` without loosening the emitted response
|
|
420
|
+
contract. Existing optional `fix` guidance is also preserved when a
|
|
421
|
+
`ProviderError` supplies it.
|
|
420
422
|
|
|
421
423
|
`details` belongs exclusively to the provider. The server passes
|
|
422
424
|
`ProviderError.options.details` through verbatim, including strings and arrays,
|
|
@@ -435,8 +437,41 @@ Treat this header as telemetry, not as provider-controlled public error detail.
|
|
|
435
437
|
Its category, taxonomy version, retryability, and optional upstream status match
|
|
436
438
|
the structured `provider_request_failed` log event.
|
|
437
439
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
+
Declare provider-owned operation failures next to their documentation. The
|
|
441
|
+
server builds a lookup once at startup and applies it to failures from that
|
|
442
|
+
operation:
|
|
443
|
+
|
|
444
|
+
```ts
|
|
445
|
+
docs: {
|
|
446
|
+
errorCodes: [{
|
|
447
|
+
code: "UPSTREAM_SCHEMA_ERROR",
|
|
448
|
+
status: 502,
|
|
449
|
+
retryable: true,
|
|
450
|
+
description: "The upstream response no longer matches its schema.",
|
|
451
|
+
}],
|
|
452
|
+
},
|
|
453
|
+
handler: async () => {
|
|
454
|
+
throw new ProviderError("Upstream schema changed", {
|
|
455
|
+
code: "UPSTREAM_SCHEMA_ERROR",
|
|
456
|
+
});
|
|
457
|
+
},
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
`defineProvider` accepts only statuses the server can emit: 400, 401, 404, 429,
|
|
461
|
+
500, 502, 503, and 504. Invalid declared statuses fail provider definition,
|
|
462
|
+
not a live request. Status selection uses this order:
|
|
463
|
+
|
|
464
|
+
1. SDK-owned errors retain SDK status semantics. Operation declarations cannot
|
|
465
|
+
override SDK-owned codes, stateful-forwarding failures, Zod/deadline errors,
|
|
466
|
+
or `TransportError` values.
|
|
467
|
+
2. A matching operation `errorCodes` entry with `status` supplies the status.
|
|
468
|
+
This slot applies to `ValidationError` as well as ordinary `ProviderError`.
|
|
469
|
+
3. The registered mappings below apply.
|
|
470
|
+
4. Existing fallbacks apply: `TransportError` 502/504, unregistered input
|
|
471
|
+
`ValidationError` 400 (output validation 500), and other unregistered
|
|
472
|
+
`ProviderError` values 500.
|
|
473
|
+
|
|
474
|
+
The registered mappings are:
|
|
440
475
|
|
|
441
476
|
| Error code or fallback | HTTP status |
|
|
442
477
|
| --- | ---: |
|
|
@@ -451,11 +486,15 @@ including `ValidationError`:
|
|
|
451
486
|
|
|
452
487
|
An unregistered non-validation `ProviderError` code returns HTTP 500 and emits
|
|
453
488
|
the greppable `unregistered_provider_error_code` signal with the code in the
|
|
454
|
-
structured failure log.
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
489
|
+
structured failure log. A matching operation declaration, including one that
|
|
490
|
+
omits `status`, makes the code registered for this signal and may independently
|
|
491
|
+
supply `retryable`. The HTTP 400 `ValidationError` behavior is only the fallback
|
|
492
|
+
when neither an operation status nor a registered mapping applies.
|
|
493
|
+
|
|
494
|
+
Throw the domain `ProviderError` directly. Subclassing or wrapping it as a
|
|
495
|
+
`TransportError` solely to preserve a 5xx response is obsolete; declare the
|
|
496
|
+
domain code's `status` instead. Genuine `TransportError` values remain
|
|
497
|
+
SDK-owned and keep their 502/504 mapping.
|
|
459
498
|
|
|
460
499
|
### Declared secrets are SDK-enforced
|
|
461
500
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @apifuse/provider-sdk Changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.0-beta.16
|
|
4
|
+
|
|
5
|
+
- Release candidate for main commit c5deb2bc31a4a4a236d27a2ce3d214b533ed358f.
|
|
6
|
+
|
|
3
7
|
## 2.2.0-beta.15
|
|
4
8
|
|
|
5
9
|
- Release candidate for main commit b5ebd25e48f6502e4ddb775d4e0a25f5c8276712.
|
|
@@ -90,6 +94,7 @@
|
|
|
90
94
|
|
|
91
95
|
## Unreleased
|
|
92
96
|
|
|
97
|
+
- Honor operation `docs.errorCodes` at runtime: declared provider-owned statuses and retryability now drive the HTTP envelope, observability header, and structured log; invalid statuses fail `defineProvider`, declared codes no longer emit the unregistered-code signal, and `TransportError` status-preservation workarounds are obsolete.
|
|
93
98
|
- Add an opt-in same-origin redirect hop policy to `ctx.http`, with bounded manual following and typed failures before a refused target is requested.
|
|
94
99
|
- Enforce provider-declared native TCP/TLS egress before proxy or socket setup, with revocable and expiring dynamic grants plus typed authorization failures; providers without a native egress declaration retain legacy behavior.
|
|
95
100
|
- **Breaking:** Provider error `details` is now passed through verbatim; SDK observability fields (`category`, `taxonomyVersion`, `upstreamStatus`, and derived `retryable`) are no longer merged into the public body. Emitted error envelopes now require top-level `retryable`, while inbound stateful forwarding tolerates an older owner response that omits it and defaults it to `false`. The removed observability metadata is available in the new `X-ApiFuse-Error-Observability` response header.
|
package/dist/define.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import ms from "ms";
|
|
2
|
+
import { SDK_RUNTIME_OWNED_ERROR_CODES } from "./error-resolution.js";
|
|
2
3
|
import { ProviderError, ValidationError } from "./errors.js";
|
|
3
4
|
import { NativeEgressPolicyValidationError, validateNativeProviderConfig, } from "./native-egress-policy.js";
|
|
4
5
|
import { safeParseSchemaSync } from "./schema.js";
|
|
5
6
|
import { resolveHealthCheckInputDateTokens } from "./server/self-test-input-tokens.js";
|
|
6
|
-
import { HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MAX, HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MIN, HEALTH_CHECK_TIMEOUT_MS_MAX, HEALTH_CHECK_TIMEOUT_MS_MIN, OPERATION_TIMEOUT_MS_MAX, OPERATION_TIMEOUT_MS_MIN, STREAM_CHUNK_BYTES_MAX, STREAM_CHUNK_BYTES_MIN, STREAM_HEARTBEAT_MS_MAX, STREAM_HEARTBEAT_MS_MIN, STREAM_IDLE_TIMEOUT_MS_MAX, STREAM_IDLE_TIMEOUT_MS_MIN, STREAM_MAX_DURATION_MS_MAX, STREAM_MAX_DURATION_MS_MIN, } from "./types.js";
|
|
7
|
+
import { HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MAX, HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MIN, HEALTH_CHECK_TIMEOUT_MS_MAX, HEALTH_CHECK_TIMEOUT_MS_MIN, OPERATION_TIMEOUT_MS_MAX, OPERATION_TIMEOUT_MS_MIN, STREAM_CHUNK_BYTES_MAX, STREAM_CHUNK_BYTES_MIN, STREAM_HEARTBEAT_MS_MAX, STREAM_HEARTBEAT_MS_MIN, STREAM_IDLE_TIMEOUT_MS_MAX, STREAM_IDLE_TIMEOUT_MS_MIN, STREAM_MAX_DURATION_MS_MAX, STREAM_MAX_DURATION_MS_MIN, VALID_OPERATION_ERROR_STATUSES, } from "./types.js";
|
|
7
8
|
const CONNECTOR_ID_REGEX = /^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*$/;
|
|
8
9
|
const OPERATION_ID_REGEX = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
|
|
9
10
|
const VALID_RUNTIMES = ["standard", "shared", "browser"];
|
|
@@ -410,6 +411,23 @@ function validateOperationObservability(providerId, operations) {
|
|
|
410
411
|
}
|
|
411
412
|
}
|
|
412
413
|
}
|
|
414
|
+
function validateOperationErrorCodes(providerId, operations) {
|
|
415
|
+
for (const [operationName, operation] of Object.entries(operations)) {
|
|
416
|
+
for (const [index, errorCode] of (operation.docs?.errorCodes ?? []).entries()) {
|
|
417
|
+
if (errorCode.status !== undefined &&
|
|
418
|
+
!VALID_OPERATION_ERROR_STATUSES.some((status) => status === errorCode.status)) {
|
|
419
|
+
const field = `operations.${operationName}.docs.errorCodes[${index}].status`;
|
|
420
|
+
throw new ValidationError(`Provider "${providerId}" has invalid ${field}: ${String(errorCode.status)} is not an emittable provider error status.`, {
|
|
421
|
+
fix: `Set ${field} to one of ${VALID_OPERATION_ERROR_STATUSES.join(", ")}, or omit it.`,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
if (errorCode.status !== undefined &&
|
|
425
|
+
SDK_RUNTIME_OWNED_ERROR_CODES.has(errorCode.code)) {
|
|
426
|
+
console.warn(`[provider-sdk] Provider "${providerId}" operation "${operationName}" declares status ${errorCode.status} for SDK-owned error code "${errorCode.code}"; the declared status is documentation-only and will be ignored at runtime.`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
413
431
|
const JSON_TRANSPORT_FIELDS = new Set(["kind"]);
|
|
414
432
|
const SSE_TRANSPORT_FIELDS = new Set([
|
|
415
433
|
"kind",
|
|
@@ -1396,6 +1414,7 @@ export function defineProvider(config) {
|
|
|
1396
1414
|
validateOperationIds(config.id, config.operations);
|
|
1397
1415
|
validateOperationAnnotations(config.id, config.operations);
|
|
1398
1416
|
validateOperationObservability(config.id, config.operations);
|
|
1417
|
+
validateOperationErrorCodes(config.id, config.operations);
|
|
1399
1418
|
validateOperationTransports(config.id, config.operations);
|
|
1400
1419
|
validateOperationContracts(config.id, config.operations);
|
|
1401
1420
|
validateToolRouterMetadata(config.id, config.operations);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// This set suppresses the unregistered-provider-error-code signal for codes
|
|
2
|
+
// intentionally emitted by SDK paths. It is not the complete authority for
|
|
3
|
+
// runtime error resolution: branded errors and additional canonical SDK codes
|
|
4
|
+
// must also remain immune to provider-declared status/retryability overrides.
|
|
5
|
+
export const SDK_OWNED_PROVIDER_ERROR_CODES = new Set([
|
|
6
|
+
"MISSING_SECRET",
|
|
7
|
+
"AUTH_PROMPT_UNAVAILABLE",
|
|
8
|
+
"BROWSER_CDP_POOL_REQUIRED",
|
|
9
|
+
"BROWSER_RUNTIME_UNSUPPORTED",
|
|
10
|
+
"STEALTH_RUNTIME_UNSUPPORTED",
|
|
11
|
+
"SSE_EVENT_UNDECLARED",
|
|
12
|
+
"STREAM_EVENT_TOO_LARGE",
|
|
13
|
+
"STREAM_CHUNK_TOO_LARGE",
|
|
14
|
+
"SSE_RESULT_UNSUPPORTED",
|
|
15
|
+
"STREAM_RESULT_UNSUPPORTED",
|
|
16
|
+
"AUTH_FLOW_NOT_CONFIGURED",
|
|
17
|
+
"refresh_not_supported",
|
|
18
|
+
"RUNTIME_UNSUPPORTED",
|
|
19
|
+
"PROVIDER_STATE_UNSUPPORTED",
|
|
20
|
+
"CHOICE_TOKEN_MASTER_SECRET_NOT_CONFIGURED",
|
|
21
|
+
"CHOICE_STATE_PAYLOAD_TOO_LARGE",
|
|
22
|
+
"CHOICE_STATE_UNAVAILABLE",
|
|
23
|
+
"CHOICE_CONTEXT_REQUIRED",
|
|
24
|
+
"unsupported_stealth_cookie_store_version",
|
|
25
|
+
"provider_secret_error",
|
|
26
|
+
"credential_key_error",
|
|
27
|
+
"credential_mode_error",
|
|
28
|
+
"flow_expired",
|
|
29
|
+
"turn_validation_error",
|
|
30
|
+
"context_access_error",
|
|
31
|
+
"UNSUPPORTED_STT_OPTION",
|
|
32
|
+
"INVALID_STT_AUDIO",
|
|
33
|
+
"STT_AUDIO_TOO_LARGE",
|
|
34
|
+
"STT_UPSTREAM_FAILED",
|
|
35
|
+
"INVALID_STT_VERIFICATION_CODE_OPTIONS",
|
|
36
|
+
"NO_CODE_FOUND",
|
|
37
|
+
"AMBIGUOUS_CODE",
|
|
38
|
+
"retry_invalid_policy",
|
|
39
|
+
"retry_unsafe_method",
|
|
40
|
+
"stealth_cookie_store_serialize_failed",
|
|
41
|
+
"response_too_large",
|
|
42
|
+
"transport_stream_unavailable",
|
|
43
|
+
"transport_invalid_method",
|
|
44
|
+
"http_transport_override_unsupported",
|
|
45
|
+
"http_redirect_policy_invalid",
|
|
46
|
+
"http_redirect_stopped",
|
|
47
|
+
"http_redirect_max_hops",
|
|
48
|
+
"http_redirect_missing_location",
|
|
49
|
+
"http_redirect_loop",
|
|
50
|
+
"transport_invalid_url",
|
|
51
|
+
"retry_exhausted",
|
|
52
|
+
"auth_abort_unsafe_data",
|
|
53
|
+
"credentials_auth_missing_credential_keys",
|
|
54
|
+
"credentials_auth_missing_credential",
|
|
55
|
+
"credentials_auth_invalid_login_result",
|
|
56
|
+
"credentials_auth_unknown_challenge",
|
|
57
|
+
"credentials_auth_unknown_pending_challenge",
|
|
58
|
+
"STATEFUL_FORWARDING_NOT_CONFIGURED",
|
|
59
|
+
"STATEFUL_FORWARDING_SIGNATURE_MISSING",
|
|
60
|
+
"STATEFUL_FORWARDING_NONCE_INVALID",
|
|
61
|
+
"STATEFUL_FORWARDING_TIMESTAMP_INVALID",
|
|
62
|
+
"STATEFUL_FORWARDING_SIGNATURE_INVALID",
|
|
63
|
+
"STATEFUL_FORWARDING_REPLAY_DETECTED",
|
|
64
|
+
"STATEFUL_FORWARDING_REPLAY_CACHE_FULL",
|
|
65
|
+
"STATEFUL_FORWARDING_ENVELOPE_INVALID",
|
|
66
|
+
"STATEFUL_FORWARDING_PROVIDER_MISMATCH",
|
|
67
|
+
"STATEFUL_FORWARDING_SOURCE_POD_MISMATCH",
|
|
68
|
+
"STATEFUL_FORWARDING_OWNER_FENCE_INVALID",
|
|
69
|
+
"STATEFUL_FORWARDING_REQUEST_FAILED",
|
|
70
|
+
"STATEFUL_FORWARDING_CONTEXT_MISSING",
|
|
71
|
+
"STATEFUL_FORWARDING_BAD_RESPONSE",
|
|
72
|
+
"STATEFUL_INTERNAL_EXECUTOR_NOT_CONFIGURED",
|
|
73
|
+
"STATEFUL_FILE_FORWARDING_UNSUPPORTED",
|
|
74
|
+
"STATEFUL_CONTROL_PLANE_OPERATION_AMBIGUOUS",
|
|
75
|
+
"STATEFUL_CONTROL_PLANE_REQUEST_FAILED",
|
|
76
|
+
"STATEFUL_CONTROL_PLANE_HTTP_ERROR",
|
|
77
|
+
"STATEFUL_CONTROL_PLANE_INVALID_RESPONSE",
|
|
78
|
+
]);
|
|
79
|
+
// Complete code authority for provider-declared runtime resolution. Keep this
|
|
80
|
+
// separate from signal suppression: declarations may document these codes, but
|
|
81
|
+
// their status and retryability can never override the SDK's canonical result.
|
|
82
|
+
export const SDK_RUNTIME_OWNED_ERROR_CODES = new Set([
|
|
83
|
+
...SDK_OWNED_PROVIDER_ERROR_CODES,
|
|
84
|
+
"reauth_required",
|
|
85
|
+
"STT_UNAVAILABLE",
|
|
86
|
+
"UNSUPPORTED_STT_BACKEND",
|
|
87
|
+
"OUTPUT_VALIDATION_FAILED",
|
|
88
|
+
"NOT_FOUND",
|
|
89
|
+
"not_found",
|
|
90
|
+
]);
|
package/dist/index.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ export { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SEN
|
|
|
37
37
|
export { createServerApp, ERROR_OBSERVABILITY_HEADER, type ServeOptions, serve, } from "./server/index.js";
|
|
38
38
|
export { getStealthProfile, listStealthProfiles } from "./stealth/profiles.js";
|
|
39
39
|
export * from "./stream.js";
|
|
40
|
-
export type { ApiFuseResponse, AuthConfig, AuthContext, AuthFlowDefinition, AuthFlowInputHandler, AuthFlowStartHandler, AuthMode, AuthTurn, Bcp47Locale, BrowserEngine, BrowserOptions, BrowserResourceBody, BrowserResourceDecision, BrowserResourceMethod, BrowserResourcePolicy, BrowserResourceRequest, BrowserResourceRoute, ConnectionMode, ContextDeclaration, CookieJar, ContextScratchpad, CredentialContext, CredentialDeclaration, E164PhoneNumber, EnvContext, FlowContext, FlowContextStore, HealthCheckAssertionContext, HealthCheckCase, HealthCheckCaseResult, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyGatewayContext, HealthJourneyJournalContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthJourneySchedule, HealthScheduleRandomization, HealthJourneySmsContext, HealthJourneyStep, HttpClient, HttpMethod, HttpRedirectFailureReason, HttpRedirectPolicy, HttpRedirectPolicyMode, HttpResponse, HttpRetryOptions, HttpRetrySummary, HttpStreamResponse, IanaTimeZone, InferSchemaOutput, Iso3166Alpha2CountryCode, Iso4217CurrencyCode, Iso8601Duration, NativeContext, NativeNetworkClient, NativeNetworkCloseReason, NativeNetworkConnection, NativeNetworkConnectInput, NativeNetworkConnectOptions, NativeNetworkDynamicGrantOptions, NativeNetworkEgressGrant, NativeProviderConfig, NativeProviderContext, NativeProxyDrainHandler, NativeProxyEgressInfo, NativeProxyExpiringEvent, NativeProxyExpiringReason, NativeTcpDynamicEgressRule, NativeTcpEgressGrant, NativeTcpEgressRule, NativeTcpPortRange, NativeTcpTlsMode, NativeTlsConnectOptions, OperationAnnotations, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDeprecationMetadata, OperationDocMeta, OperationErrorCode, OperationHandlerResult, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, OperationTransportKind, ProbeInterval, ProviderAccessConfig, ProviderAccessVisibility, ProviderCache, ProviderCacheGetOrSetOptions, ProviderCacheKeyOptions, ProviderCacheLookupMeta, ProviderCacheResponseMeta, ProviderCacheResult, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderDeploymentOverrides, ProviderFileRef, ProviderFilesContext, ProviderHealthMonitorConfig, ProviderHealthProbeConfig, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderLogoSource, ProviderMeta, ProviderProxyConfig, ProviderProxyMode, ProviderProxyPolicy, ProviderProxyProvider, ProviderProxySessionAffinity, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderReviewed, ProviderResolvedFile, ProviderRuntimeState, ProviderSecretDeclaration, ProviderStateDurationString, ProviderStateNamespace, ProviderStreamEvent, ProviderSttConfig, ProviderSttMode, ProviderSupportLevel, RequestOptions, RedirectRunReason, Rfc3339Instant, SchemaLike, SmsOrigin, SmsOtpExtractionPattern, SmsOtpMatcherDefinition, SseMessage, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, StealthClient, StealthCookieStore, StealthCookieStoreV1, StealthFetchOptions, StealthPlatform, StealthProfile, StealthRedirectHop, StealthRedirectRunOptions, StealthRedirectRunResult, StealthResponse, StealthSession, StealthSessionCookies, SttAudioInput, SttContext, SttPromptPolicy, SttSegment, SttTranscribeMode, SttTranscribeRequest, SttTranscript, SttUnsupportedOptionPolicy, SttUsage, SttVerificationCodeOptions, SttWarning, TraceConfig, TraceSpan, VerificationCodeCandidate, VerificationCodeCandidateSource, VerificationCodeExtractionResult, } from "./types.js";
|
|
40
|
+
export type { ApiFuseResponse, AuthConfig, AuthContext, AuthFlowDefinition, AuthFlowInputHandler, AuthFlowStartHandler, AuthMode, AuthTurn, Bcp47Locale, BrowserEngine, BrowserOptions, BrowserResourceBody, BrowserResourceDecision, BrowserResourceMethod, BrowserResourcePolicy, BrowserResourceRequest, BrowserResourceRoute, ConnectionMode, ContextDeclaration, CookieJar, ContextScratchpad, CredentialContext, CredentialDeclaration, E164PhoneNumber, EnvContext, FlowContext, FlowContextStore, HealthCheckAssertionContext, HealthCheckCase, HealthCheckCaseResult, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyGatewayContext, HealthJourneyJournalContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthJourneySchedule, HealthScheduleRandomization, HealthJourneySmsContext, HealthJourneyStep, HttpClient, HttpMethod, HttpRedirectFailureReason, HttpRedirectPolicy, HttpRedirectPolicyMode, HttpResponse, HttpRetryOptions, HttpRetrySummary, HttpStreamResponse, IanaTimeZone, InferSchemaOutput, Iso3166Alpha2CountryCode, Iso4217CurrencyCode, Iso8601Duration, NativeContext, NativeNetworkClient, NativeNetworkCloseReason, NativeNetworkConnection, NativeNetworkConnectInput, NativeNetworkConnectOptions, NativeNetworkDynamicGrantOptions, NativeNetworkEgressGrant, NativeProviderConfig, NativeProviderContext, NativeProxyDrainHandler, NativeProxyEgressInfo, NativeProxyExpiringEvent, NativeProxyExpiringReason, NativeTcpDynamicEgressRule, NativeTcpEgressGrant, NativeTcpEgressRule, NativeTcpPortRange, NativeTcpTlsMode, NativeTlsConnectOptions, OperationAnnotations, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDeprecationMetadata, OperationDocMeta, OperationErrorCode, ProviderErrorStatus, OperationHandlerResult, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, OperationTransportKind, ProbeInterval, ProviderAccessConfig, ProviderAccessVisibility, ProviderCache, ProviderCacheGetOrSetOptions, ProviderCacheKeyOptions, ProviderCacheLookupMeta, ProviderCacheResponseMeta, ProviderCacheResult, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderDeploymentOverrides, ProviderFileRef, ProviderFilesContext, ProviderHealthMonitorConfig, ProviderHealthProbeConfig, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderLogoSource, ProviderMeta, ProviderProxyConfig, ProviderProxyMode, ProviderProxyPolicy, ProviderProxyProvider, ProviderProxySessionAffinity, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderReviewed, ProviderResolvedFile, ProviderRuntimeState, ProviderSecretDeclaration, ProviderStateDurationString, ProviderStateNamespace, ProviderStreamEvent, ProviderSttConfig, ProviderSttMode, ProviderSupportLevel, RequestOptions, RedirectRunReason, Rfc3339Instant, SchemaLike, SmsOrigin, SmsOtpExtractionPattern, SmsOtpMatcherDefinition, SseMessage, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, StealthClient, StealthCookieStore, StealthCookieStoreV1, StealthFetchOptions, StealthPlatform, StealthProfile, StealthRedirectHop, StealthRedirectRunOptions, StealthRedirectRunResult, StealthResponse, StealthSession, StealthSessionCookies, SttAudioInput, SttContext, SttPromptPolicy, SttSegment, SttTranscribeMode, SttTranscribeRequest, SttTranscript, SttUnsupportedOptionPolicy, SttUsage, SttVerificationCodeOptions, SttWarning, TraceConfig, TraceSpan, VerificationCodeCandidate, VerificationCodeCandidateSource, VerificationCodeExtractionResult, } from "./types.js";
|
|
41
41
|
export { DEFAULT_OPERATION_TRANSPORT, HttpRetryAfterPolicy, HttpRetryDelayStrategy, HttpRetryJitter, HttpRetryPreset, HttpRetryUnsafeMethodPolicy, PROBE_INTERVALS, STREAM_CHUNK_BYTES_MAX, STREAM_CHUNK_BYTES_MIN, STREAM_HEARTBEAT_MS_MAX, STREAM_HEARTBEAT_MS_MIN, STREAM_IDLE_TIMEOUT_MS_MAX, STREAM_IDLE_TIMEOUT_MS_MIN, STREAM_MAX_DURATION_MS_MAX, STREAM_MAX_DURATION_MS_MIN, } from "./types.js";
|
|
42
42
|
export * from "./utils/date.js";
|
|
43
43
|
export * from "./utils/parse.js";
|
package/dist/provider.d.ts
CHANGED
|
@@ -7,6 +7,6 @@ export { AuthError, HttpRedirectError, isProviderError, isSessionExpiredError, i
|
|
|
7
7
|
export { getProviderLocalePath, providerLocaleKey, qualifyProviderLocaleKey, } from "./i18n/index.js";
|
|
8
8
|
export { type CreateProviderChoiceContextOptions, createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, } from "./runtime/choice.js";
|
|
9
9
|
export { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, redactPayload, type SensitiveFieldKind, type SensitiveFieldOptions, type SensitivePath, sensitive, z, } from "./schema.js";
|
|
10
|
-
export type { AuthAbortData, AuthAbortRetry, AuthFlowTerminalContext, AuthMode, AuthSafeData, AuthSafeJson, FlowContext, HealthCheckAssertionContext, HealthCheckCase, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthScheduleRandomization, HttpRedirectFailureReason, HttpRedirectPolicy, HttpRedirectPolicyMode, HttpRetryOptions, HttpRetrySummary, InferSchemaOutput, NativeContext, NativeNetworkClient, NativeNetworkCloseReason, NativeNetworkConnection, NativeNetworkConnectInput, NativeNetworkConnectOptions, NativeNetworkDynamicGrantOptions, NativeNetworkEgressGrant, NativeProviderConfig, NativeProviderContext, NativeProxyDrainHandler, NativeProxyEgressInfo, NativeProxyExpiringEvent, NativeProxyExpiringReason, NativeTcpDynamicEgressRule, NativeTcpEgressGrant, NativeTcpEgressRule, NativeTcpPortRange, NativeTcpTlsMode, NativeTlsConnectOptions, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDocMeta, OperationErrorCode, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, ProviderAccessVisibility, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderDeploymentOverrides, ProviderFileRef, ProviderFilesContext, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderProxyPolicy, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderResolvedFile, ProviderRuntimeState, ProviderStateDurationString, ProviderStateNamespace, ProviderSupportLevel, RedirectRunReason, SchemaLike, SmsOtpMatcherDefinition, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, } from "./types.js";
|
|
10
|
+
export type { AuthAbortData, AuthAbortRetry, AuthFlowTerminalContext, AuthMode, AuthSafeData, AuthSafeJson, FlowContext, HealthCheckAssertionContext, HealthCheckCase, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthScheduleRandomization, HttpRedirectFailureReason, HttpRedirectPolicy, HttpRedirectPolicyMode, HttpRetryOptions, HttpRetrySummary, InferSchemaOutput, NativeContext, NativeNetworkClient, NativeNetworkCloseReason, NativeNetworkConnection, NativeNetworkConnectInput, NativeNetworkConnectOptions, NativeNetworkDynamicGrantOptions, NativeNetworkEgressGrant, NativeProviderConfig, NativeProviderContext, NativeProxyDrainHandler, NativeProxyEgressInfo, NativeProxyExpiringEvent, NativeProxyExpiringReason, NativeTcpDynamicEgressRule, NativeTcpEgressGrant, NativeTcpEgressRule, NativeTcpPortRange, NativeTcpTlsMode, NativeTlsConnectOptions, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDocMeta, OperationErrorCode, ProviderErrorStatus, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, ProviderAccessVisibility, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderDeploymentOverrides, ProviderFileRef, ProviderFilesContext, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderProxyPolicy, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderResolvedFile, ProviderRuntimeState, ProviderStateDurationString, ProviderStateNamespace, ProviderSupportLevel, RedirectRunReason, SchemaLike, SmsOtpMatcherDefinition, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, } from "./types.js";
|
|
11
11
|
export { createNativeNetworkClient, deriveNativeCredentialAffinityKey, NativeEgressGrantExpiredError, NativeEgressNotDeclaredError, NativeIdleTimeoutError, NativeNetworkError, NativeProxyExpiredError, resolveNativeGatewayProxy, type NativeGatewayProxy, type NativeGatewayProxyResolutionInput, type NativeGatewayProxySynthesizer, type NativeGatewayProxySynthesisInput, type NativeNetworkClientOptions, type NativeNetworkErrorCode, } from "./runtime/native-network.js";
|
|
12
12
|
export { HttpRetryAfterPolicy, HttpRetryDelayStrategy, HttpRetryJitter, HttpRetryPreset, HttpRetryUnsafeMethodPolicy, } from "./types.js";
|
package/dist/server/serve.js
CHANGED
|
@@ -3,6 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
import { Hono } from "hono";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { AuthAbortError, createAuthFlowHelpers } from "../auth.js";
|
|
6
|
+
import { SDK_OWNED_PROVIDER_ERROR_CODES, SDK_RUNTIME_OWNED_ERROR_CODES, } from "../error-resolution.js";
|
|
6
7
|
import { AuthError, isProviderError, isSessionExpiredError, isTransportError, isValidationError, ProviderError, } from "../errors.js";
|
|
7
8
|
import { loadProviderLocaleCatalogs, localizeAuthTurn, } from "../i18n/catalog.js";
|
|
8
9
|
import { categoryForStatus, isRetryableCategory, PROVIDER_OBSERVABILITY_TAXONOMY_VERSION, } from "../observability.js";
|
|
@@ -29,6 +30,7 @@ import { STATEFUL_NONCE_HEADER as STATEFUL_FORWARDING_NONCE_HEADER, STATEFUL_SIG
|
|
|
29
30
|
import { StatefulRoutingDeadlineError } from "../stateful/stateful-provider-session-routing.js";
|
|
30
31
|
import { getStealthProfile } from "../stealth/profiles.js";
|
|
31
32
|
import { APIFUSE_STREAM_DONE_EVENT, APIFUSE_STREAM_ERROR_EVENT, encodeSseEvent, error as streamError, } from "../stream.js";
|
|
33
|
+
import { VALID_OPERATION_ERROR_STATUSES } from "../types.js";
|
|
32
34
|
import { createSelfTestApp, createSelfTestAuthFlowInvoke, createSelfTestInvoke, resolveSelfTestPort, } from "./self-test.js";
|
|
33
35
|
import { resolveSelfTestMasterSecrets } from "./self-test-token.js";
|
|
34
36
|
import { AuthFlowRequestSchema, OperationConnectionSchema, OperationRequestSchema, } from "./types.js";
|
|
@@ -353,8 +355,8 @@ function zodDetails(error) {
|
|
|
353
355
|
message: issue.message,
|
|
354
356
|
}));
|
|
355
357
|
}
|
|
356
|
-
function toErrorResponse(error, requestId) {
|
|
357
|
-
const observability = errorObservabilityDetails(error);
|
|
358
|
+
function toErrorResponse(error, requestId, declaredErrorCode) {
|
|
359
|
+
const observability = errorObservabilityDetails(error, declaredErrorCode);
|
|
358
360
|
if (error instanceof StatefulRoutingDeadlineError) {
|
|
359
361
|
return {
|
|
360
362
|
error: {
|
|
@@ -414,7 +416,10 @@ function toErrorResponse(error, requestId) {
|
|
|
414
416
|
// narrowing from a ProviderError-typed value would collapse the negative branch
|
|
415
417
|
// to `never`. Narrowing from unknown avoids that while still recognizing errors
|
|
416
418
|
// from a duplicate SDK module instance.
|
|
417
|
-
function providerObservabilityDetails(error) {
|
|
419
|
+
function providerObservabilityDetails(error, declaredErrorCode) {
|
|
420
|
+
const declaredRetryable = sdkOwnsErrorResolution(error)
|
|
421
|
+
? undefined
|
|
422
|
+
: declaredErrorCode?.retryable;
|
|
418
423
|
// Session-expiry surfaces the credential_expired category + the opt-in
|
|
419
424
|
// retryable signal so Gateway/Credential Service can refresh and re-drive the
|
|
420
425
|
// operation (see design.md §4.3 D3). Without this branch the auth error would
|
|
@@ -424,7 +429,7 @@ function providerObservabilityDetails(error) {
|
|
|
424
429
|
return {
|
|
425
430
|
category: error.options?.category ?? "credential_expired",
|
|
426
431
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
427
|
-
retryable: error.options?.retryable ?? false,
|
|
432
|
+
retryable: error.options?.retryable ?? declaredRetryable ?? false,
|
|
428
433
|
};
|
|
429
434
|
}
|
|
430
435
|
// Missing-secret errors carry the canonical credential_unavailable category
|
|
@@ -436,7 +441,7 @@ function providerObservabilityDetails(error) {
|
|
|
436
441
|
return {
|
|
437
442
|
category: error.options?.category ?? "credential_unavailable",
|
|
438
443
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
439
|
-
retryable: error.options?.retryable ?? false,
|
|
444
|
+
retryable: error.options?.retryable ?? declaredRetryable ?? false,
|
|
440
445
|
};
|
|
441
446
|
}
|
|
442
447
|
if (!isTransportError(error)) {
|
|
@@ -467,17 +472,23 @@ function providerObservabilityDetails(error) {
|
|
|
467
472
|
...(error.upstreamStatus ? { upstreamStatus: error.upstreamStatus } : {}),
|
|
468
473
|
};
|
|
469
474
|
}
|
|
470
|
-
function errorObservabilityDetails(error) {
|
|
471
|
-
const
|
|
475
|
+
function errorObservabilityDetails(error, declaredErrorCode) {
|
|
476
|
+
const effectiveDeclaration = sdkOwnsErrorResolution(error) ? undefined : declaredErrorCode;
|
|
477
|
+
const providerDetails = providerObservabilityDetails(error, effectiveDeclaration);
|
|
472
478
|
if (providerDetails)
|
|
473
479
|
return providerDetails;
|
|
474
480
|
if (error instanceof z.ZodError || isValidationError(error)) {
|
|
481
|
+
const declaredStatus = effectiveDeclaration?.status;
|
|
475
482
|
return {
|
|
476
483
|
category: isProviderError(error) && error.options?.category
|
|
477
484
|
? error.options.category
|
|
478
|
-
:
|
|
485
|
+
: isEmittableErrorStatus(declaredStatus) && declaredStatus >= 500
|
|
486
|
+
? "provider_error"
|
|
487
|
+
: "input_validation",
|
|
479
488
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
480
|
-
retryable: isProviderError(error)
|
|
489
|
+
retryable: isProviderError(error)
|
|
490
|
+
? (error.options?.retryable ?? effectiveDeclaration?.retryable ?? false)
|
|
491
|
+
: false,
|
|
481
492
|
};
|
|
482
493
|
}
|
|
483
494
|
if (error instanceof StatefulRoutingDeadlineError) {
|
|
@@ -491,7 +502,7 @@ function errorObservabilityDetails(error) {
|
|
|
491
502
|
return {
|
|
492
503
|
category: error.options?.category ?? "provider_error",
|
|
493
504
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
494
|
-
retryable: error.options?.retryable ?? false,
|
|
505
|
+
retryable: error.options?.retryable ?? effectiveDeclaration?.retryable ?? false,
|
|
495
506
|
};
|
|
496
507
|
}
|
|
497
508
|
return {
|
|
@@ -500,9 +511,9 @@ function errorObservabilityDetails(error) {
|
|
|
500
511
|
retryable: false,
|
|
501
512
|
};
|
|
502
513
|
}
|
|
503
|
-
function responseWithErrorObservability(response, error) {
|
|
514
|
+
function responseWithErrorObservability(response, error, declaredErrorCode) {
|
|
504
515
|
const headers = new Headers(response.headers);
|
|
505
|
-
headers.set(ERROR_OBSERVABILITY_HEADER, JSON.stringify(errorObservabilityDetails(error)));
|
|
516
|
+
headers.set(ERROR_OBSERVABILITY_HEADER, JSON.stringify(errorObservabilityDetails(error, declaredErrorCode)));
|
|
506
517
|
return new Response(response.body, {
|
|
507
518
|
status: response.status,
|
|
508
519
|
statusText: response.statusText,
|
|
@@ -534,7 +545,11 @@ function publicProviderErrorMessage(error) {
|
|
|
534
545
|
}
|
|
535
546
|
return error.message;
|
|
536
547
|
}
|
|
537
|
-
function
|
|
548
|
+
function isEmittableErrorStatus(value) {
|
|
549
|
+
return (typeof value === "number" &&
|
|
550
|
+
VALID_OPERATION_ERROR_STATUSES.some((status) => status === value));
|
|
551
|
+
}
|
|
552
|
+
function toStatusCode(error, declaredErrorCode) {
|
|
538
553
|
if (error instanceof z.ZodError) {
|
|
539
554
|
return 400;
|
|
540
555
|
}
|
|
@@ -542,6 +557,10 @@ function toStatusCode(error) {
|
|
|
542
557
|
return 504;
|
|
543
558
|
}
|
|
544
559
|
if (isProviderError(error)) {
|
|
560
|
+
if (!sdkOwnsErrorResolution(error) &&
|
|
561
|
+
isEmittableErrorStatus(declaredErrorCode?.status)) {
|
|
562
|
+
return declaredErrorCode.status;
|
|
563
|
+
}
|
|
545
564
|
switch (error.code) {
|
|
546
565
|
case "AUTH_REQUIRED":
|
|
547
566
|
case "reauth_required":
|
|
@@ -576,82 +595,32 @@ function toStatusCode(error) {
|
|
|
576
595
|
}
|
|
577
596
|
return 500;
|
|
578
597
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
"
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
"turn_validation_error",
|
|
606
|
-
"context_access_error",
|
|
607
|
-
"UNSUPPORTED_STT_OPTION",
|
|
608
|
-
"INVALID_STT_AUDIO",
|
|
609
|
-
"STT_AUDIO_TOO_LARGE",
|
|
610
|
-
"STT_UPSTREAM_FAILED",
|
|
611
|
-
"INVALID_STT_VERIFICATION_CODE_OPTIONS",
|
|
612
|
-
"NO_CODE_FOUND",
|
|
613
|
-
"AMBIGUOUS_CODE",
|
|
614
|
-
"retry_invalid_policy",
|
|
615
|
-
"retry_unsafe_method",
|
|
616
|
-
"stealth_cookie_store_serialize_failed",
|
|
617
|
-
"response_too_large",
|
|
618
|
-
"transport_stream_unavailable",
|
|
619
|
-
"transport_invalid_method",
|
|
620
|
-
"http_transport_override_unsupported",
|
|
621
|
-
"http_redirect_policy_invalid",
|
|
622
|
-
"http_redirect_stopped",
|
|
623
|
-
"http_redirect_max_hops",
|
|
624
|
-
"http_redirect_missing_location",
|
|
625
|
-
"http_redirect_loop",
|
|
626
|
-
"transport_invalid_url",
|
|
627
|
-
"retry_exhausted",
|
|
628
|
-
"auth_abort_unsafe_data",
|
|
629
|
-
"credentials_auth_missing_credential_keys",
|
|
630
|
-
"credentials_auth_missing_credential",
|
|
631
|
-
"credentials_auth_invalid_login_result",
|
|
632
|
-
"credentials_auth_unknown_challenge",
|
|
633
|
-
"credentials_auth_unknown_pending_challenge",
|
|
634
|
-
"STATEFUL_FORWARDING_NOT_CONFIGURED",
|
|
635
|
-
"STATEFUL_FORWARDING_SIGNATURE_MISSING",
|
|
636
|
-
"STATEFUL_FORWARDING_NONCE_INVALID",
|
|
637
|
-
"STATEFUL_FORWARDING_TIMESTAMP_INVALID",
|
|
638
|
-
"STATEFUL_FORWARDING_SIGNATURE_INVALID",
|
|
639
|
-
"STATEFUL_FORWARDING_REPLAY_DETECTED",
|
|
640
|
-
"STATEFUL_FORWARDING_REPLAY_CACHE_FULL",
|
|
641
|
-
"STATEFUL_FORWARDING_ENVELOPE_INVALID",
|
|
642
|
-
"STATEFUL_FORWARDING_PROVIDER_MISMATCH",
|
|
643
|
-
"STATEFUL_FORWARDING_SOURCE_POD_MISMATCH",
|
|
644
|
-
"STATEFUL_FORWARDING_OWNER_FENCE_INVALID",
|
|
645
|
-
"STATEFUL_FORWARDING_REQUEST_FAILED",
|
|
646
|
-
"STATEFUL_FORWARDING_CONTEXT_MISSING",
|
|
647
|
-
"STATEFUL_FORWARDING_BAD_RESPONSE",
|
|
648
|
-
"STATEFUL_INTERNAL_EXECUTOR_NOT_CONFIGURED",
|
|
649
|
-
"STATEFUL_FILE_FORWARDING_UNSUPPORTED",
|
|
650
|
-
"STATEFUL_CONTROL_PLANE_OPERATION_AMBIGUOUS",
|
|
651
|
-
"STATEFUL_CONTROL_PLANE_REQUEST_FAILED",
|
|
652
|
-
"STATEFUL_CONTROL_PLANE_HTTP_ERROR",
|
|
653
|
-
"STATEFUL_CONTROL_PLANE_INVALID_RESPONSE",
|
|
654
|
-
]);
|
|
598
|
+
function sdkOwnsErrorResolution(error) {
|
|
599
|
+
if (isSessionExpiredError(error))
|
|
600
|
+
return true;
|
|
601
|
+
if (isTransportError(error))
|
|
602
|
+
return true;
|
|
603
|
+
if (error instanceof z.ZodError)
|
|
604
|
+
return true;
|
|
605
|
+
if (error instanceof StatefulRoutingDeadlineError)
|
|
606
|
+
return true;
|
|
607
|
+
return (isProviderError(error) &&
|
|
608
|
+
typeof error.code === "string" &&
|
|
609
|
+
SDK_RUNTIME_OWNED_ERROR_CODES.has(error.code));
|
|
610
|
+
}
|
|
611
|
+
function buildOperationErrorCodeLookup(provider) {
|
|
612
|
+
return new Map(Object.entries(provider.operations).flatMap(([operationId, operation]) => {
|
|
613
|
+
const errorCodes = operation.docs?.errorCodes;
|
|
614
|
+
return errorCodes?.length
|
|
615
|
+
? [[operationId, new Map(errorCodes.map((entry) => [entry.code, entry]))]]
|
|
616
|
+
: [];
|
|
617
|
+
}));
|
|
618
|
+
}
|
|
619
|
+
function declaredErrorCodeFor(error, operationId, lookup) {
|
|
620
|
+
if (!operationId || !isProviderError(error) || typeof error.code !== "string")
|
|
621
|
+
return undefined;
|
|
622
|
+
return lookup.get(operationId)?.get(error.code);
|
|
623
|
+
}
|
|
655
624
|
function extractRequestId(raw) {
|
|
656
625
|
if (!raw || typeof raw !== "object") {
|
|
657
626
|
return undefined;
|
|
@@ -659,7 +628,7 @@ function extractRequestId(raw) {
|
|
|
659
628
|
const value = Object.getOwnPropertyDescriptor(raw, "requestId")?.value;
|
|
660
629
|
return typeof value === "string" ? value : undefined;
|
|
661
630
|
}
|
|
662
|
-
function logProviderError(logger, provider, kind, route, requestId, error, status, cost) {
|
|
631
|
+
function logProviderError(logger, provider, kind, route, requestId, error, status, cost, declaredErrorCode) {
|
|
663
632
|
const code = isProviderError(error)
|
|
664
633
|
? (error.code ?? "provider_error")
|
|
665
634
|
: error instanceof z.ZodError
|
|
@@ -669,12 +638,13 @@ function logProviderError(logger, provider, kind, route, requestId, error, statu
|
|
|
669
638
|
: "internal_error";
|
|
670
639
|
const errorClass = error instanceof Error ? error.name : typeof error;
|
|
671
640
|
const message = error instanceof Error ? error.message : String(error);
|
|
672
|
-
const details = errorObservabilityDetails(error);
|
|
641
|
+
const details = errorObservabilityDetails(error, declaredErrorCode);
|
|
673
642
|
const isUnregisteredProviderErrorCode = status === 500 &&
|
|
674
643
|
isProviderError(error) &&
|
|
675
644
|
!isValidationError(error) &&
|
|
676
645
|
typeof error.code === "string" &&
|
|
677
|
-
!SDK_OWNED_PROVIDER_ERROR_CODES.has(error.code)
|
|
646
|
+
!SDK_OWNED_PROVIDER_ERROR_CODES.has(error.code) &&
|
|
647
|
+
declaredErrorCode === undefined;
|
|
678
648
|
const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
|
|
679
649
|
emit({
|
|
680
650
|
level: status >= 500 ? "error" : "warn",
|
|
@@ -1270,6 +1240,7 @@ export function createServerApp(provider, options = {}) {
|
|
|
1270
1240
|
validateStatefulServerConfig(options);
|
|
1271
1241
|
const app = new Hono();
|
|
1272
1242
|
const logger = options.logger ?? defaultProviderServerLogger;
|
|
1243
|
+
const operationErrorCodes = buildOperationErrorCodeLookup(provider);
|
|
1273
1244
|
const statefulForwardingReplayCache = new StatefulForwardingReplayCache(options.statefulForwarding?.replayCacheMaxEntries ??
|
|
1274
1245
|
DEFAULT_STATEFUL_FORWARDING_REPLAY_CACHE_MAX_ENTRIES);
|
|
1275
1246
|
const state = options.state ??
|
|
@@ -1303,6 +1274,7 @@ export function createServerApp(provider, options = {}) {
|
|
|
1303
1274
|
app.post(STATEFUL_INTERNAL_OPERATIONS_ROUTE, async (c) => {
|
|
1304
1275
|
let rawBodyText = "";
|
|
1305
1276
|
let rawBody;
|
|
1277
|
+
let operationId;
|
|
1306
1278
|
const operation = "stateful-internal";
|
|
1307
1279
|
const requestCost = startRequestCost();
|
|
1308
1280
|
try {
|
|
@@ -1388,7 +1360,7 @@ export function createServerApp(provider, options = {}) {
|
|
|
1388
1360
|
});
|
|
1389
1361
|
}
|
|
1390
1362
|
const request = operationRequestFromForwardingEnvelope(envelope);
|
|
1391
|
-
|
|
1363
|
+
operationId = envelope.operationId;
|
|
1392
1364
|
const ctx = createProviderContext(provider, request, operationId, options, state);
|
|
1393
1365
|
if (deadlineAtMs !== undefined && deadlineAtMs <= Date.now()) {
|
|
1394
1366
|
throw new StatefulRoutingDeadlineError(envelope.requestId, envelope.deadlineAt);
|
|
@@ -1405,13 +1377,14 @@ export function createServerApp(provider, options = {}) {
|
|
|
1405
1377
|
return c.json({ data: output });
|
|
1406
1378
|
}
|
|
1407
1379
|
catch (error) {
|
|
1408
|
-
const
|
|
1380
|
+
const declaredErrorCode = declaredErrorCodeFor(error, operationId, operationErrorCodes);
|
|
1381
|
+
const status = toStatusCode(error, declaredErrorCode);
|
|
1409
1382
|
if (isProviderError(error) && error.code === "STATEFUL_FORWARDING_REPLAY_CACHE_FULL") {
|
|
1410
1383
|
c.header("Retry-After", String(STATEFUL_FORWARDING_REPLAY_RETRY_AFTER_SECONDS));
|
|
1411
1384
|
}
|
|
1412
1385
|
const requestId = extractRequestId(rawBody);
|
|
1413
|
-
logProviderError(logger, provider, "operation", operation, requestId, error, status, finishRequestCost(requestCost));
|
|
1414
|
-
return responseWithErrorObservability(c.json(toErrorResponse(error, requestId), status), error);
|
|
1386
|
+
logProviderError(logger, provider, "operation", operationId || operation, requestId, error, status, finishRequestCost(requestCost), declaredErrorCode);
|
|
1387
|
+
return responseWithErrorObservability(c.json(toErrorResponse(error, requestId, declaredErrorCode), status), error, declaredErrorCode);
|
|
1415
1388
|
}
|
|
1416
1389
|
});
|
|
1417
1390
|
app.post("/v1/:operation", async (c) => {
|
|
@@ -1439,13 +1412,14 @@ export function createServerApp(provider, options = {}) {
|
|
|
1439
1412
|
return c.json(response);
|
|
1440
1413
|
}
|
|
1441
1414
|
catch (error) {
|
|
1442
|
-
const
|
|
1415
|
+
const declaredErrorCode = declaredErrorCodeFor(error, operation, operationErrorCodes);
|
|
1416
|
+
const status = toStatusCode(error, declaredErrorCode);
|
|
1443
1417
|
const requestId = extractRequestId(rawBody);
|
|
1444
|
-
logProviderError(logger, provider, "operation", operation, requestId, error, status, finishRequestCost(requestCost));
|
|
1418
|
+
logProviderError(logger, provider, "operation", operation, requestId, error, status, finishRequestCost(requestCost), declaredErrorCode);
|
|
1445
1419
|
const telemetryHeader = proxyTelemetry.toHeaderValue();
|
|
1446
1420
|
if (telemetryHeader)
|
|
1447
1421
|
c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
|
|
1448
|
-
return responseWithErrorObservability(c.json(toErrorResponse(error, requestId), status), error);
|
|
1422
|
+
return responseWithErrorObservability(c.json(toErrorResponse(error, requestId, declaredErrorCode), status), error, declaredErrorCode);
|
|
1449
1423
|
}
|
|
1450
1424
|
});
|
|
1451
1425
|
app.post("/auth/start", async (c) => {
|
package/dist/types.d.ts
CHANGED
|
@@ -610,9 +610,11 @@ export interface HealthMonitorProbeOverride {
|
|
|
610
610
|
/** Optional degraded threshold override for generated registry probes. */
|
|
611
611
|
degradedThresholdMs?: number;
|
|
612
612
|
}
|
|
613
|
+
export declare const VALID_OPERATION_ERROR_STATUSES: readonly [400, 401, 404, 429, 500, 502, 503, 504];
|
|
614
|
+
export type ProviderErrorStatus = (typeof VALID_OPERATION_ERROR_STATUSES)[number];
|
|
613
615
|
export interface OperationErrorCode {
|
|
614
616
|
code: string;
|
|
615
|
-
status?:
|
|
617
|
+
status?: ProviderErrorStatus;
|
|
616
618
|
description: string;
|
|
617
619
|
retryable?: boolean;
|
|
618
620
|
}
|
package/dist/types.js
CHANGED
|
@@ -32,6 +32,7 @@ export const HEALTH_CHECK_TIMEOUT_MS_MIN = 1;
|
|
|
32
32
|
export const HEALTH_CHECK_TIMEOUT_MS_MAX = 60_000;
|
|
33
33
|
export const HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MIN = 1;
|
|
34
34
|
export const HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MAX = 60_000;
|
|
35
|
+
export const VALID_OPERATION_ERROR_STATUSES = [400, 401, 404, 429, 500, 502, 503, 504];
|
|
35
36
|
export const HttpRetryPreset = {
|
|
36
37
|
Off: "off",
|
|
37
38
|
TransportTransient: "transport_transient",
|
package/package.json
CHANGED
package/src/define.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ms from "ms";
|
|
2
2
|
|
|
3
|
+
import { SDK_RUNTIME_OWNED_ERROR_CODES } from "./error-resolution.js";
|
|
3
4
|
import { ProviderError, ValidationError } from "./errors.js";
|
|
4
5
|
import {
|
|
5
6
|
NativeEgressPolicyValidationError,
|
|
@@ -56,6 +57,7 @@ import {
|
|
|
56
57
|
STREAM_IDLE_TIMEOUT_MS_MIN,
|
|
57
58
|
STREAM_MAX_DURATION_MS_MAX,
|
|
58
59
|
STREAM_MAX_DURATION_MS_MIN,
|
|
60
|
+
VALID_OPERATION_ERROR_STATUSES,
|
|
59
61
|
} from "./types.js";
|
|
60
62
|
|
|
61
63
|
type ProviderImplementationSourceAccess =
|
|
@@ -800,6 +802,36 @@ function validateOperationObservability(
|
|
|
800
802
|
}
|
|
801
803
|
}
|
|
802
804
|
|
|
805
|
+
function validateOperationErrorCodes(
|
|
806
|
+
providerId: string,
|
|
807
|
+
operations: Record<string, ProviderOperation>,
|
|
808
|
+
): void {
|
|
809
|
+
for (const [operationName, operation] of Object.entries(operations)) {
|
|
810
|
+
for (const [index, errorCode] of (operation.docs?.errorCodes ?? []).entries()) {
|
|
811
|
+
if (
|
|
812
|
+
errorCode.status !== undefined &&
|
|
813
|
+
!VALID_OPERATION_ERROR_STATUSES.some((status) => status === errorCode.status)
|
|
814
|
+
) {
|
|
815
|
+
const field = `operations.${operationName}.docs.errorCodes[${index}].status`;
|
|
816
|
+
throw new ValidationError(
|
|
817
|
+
`Provider "${providerId}" has invalid ${field}: ${String(errorCode.status)} is not an emittable provider error status.`,
|
|
818
|
+
{
|
|
819
|
+
fix: `Set ${field} to one of ${VALID_OPERATION_ERROR_STATUSES.join(", ")}, or omit it.`,
|
|
820
|
+
},
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
if (
|
|
824
|
+
errorCode.status !== undefined &&
|
|
825
|
+
SDK_RUNTIME_OWNED_ERROR_CODES.has(errorCode.code)
|
|
826
|
+
) {
|
|
827
|
+
console.warn(
|
|
828
|
+
`[provider-sdk] Provider "${providerId}" operation "${operationName}" declares status ${errorCode.status} for SDK-owned error code "${errorCode.code}"; the declared status is documentation-only and will be ignored at runtime.`,
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
803
835
|
const JSON_TRANSPORT_FIELDS = new Set(["kind"]);
|
|
804
836
|
const SSE_TRANSPORT_FIELDS = new Set([
|
|
805
837
|
"kind",
|
|
@@ -2185,6 +2217,7 @@ export function defineProvider<
|
|
|
2185
2217
|
validateOperationIds(config.id, config.operations);
|
|
2186
2218
|
validateOperationAnnotations(config.id, config.operations);
|
|
2187
2219
|
validateOperationObservability(config.id, config.operations);
|
|
2220
|
+
validateOperationErrorCodes(config.id, config.operations);
|
|
2188
2221
|
validateOperationTransports(config.id, config.operations);
|
|
2189
2222
|
validateOperationContracts(config.id, config.operations);
|
|
2190
2223
|
validateToolRouterMetadata(config.id, config.operations);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// This set suppresses the unregistered-provider-error-code signal for codes
|
|
2
|
+
// intentionally emitted by SDK paths. It is not the complete authority for
|
|
3
|
+
// runtime error resolution: branded errors and additional canonical SDK codes
|
|
4
|
+
// must also remain immune to provider-declared status/retryability overrides.
|
|
5
|
+
export const SDK_OWNED_PROVIDER_ERROR_CODES = new Set([
|
|
6
|
+
"MISSING_SECRET",
|
|
7
|
+
"AUTH_PROMPT_UNAVAILABLE",
|
|
8
|
+
"BROWSER_CDP_POOL_REQUIRED",
|
|
9
|
+
"BROWSER_RUNTIME_UNSUPPORTED",
|
|
10
|
+
"STEALTH_RUNTIME_UNSUPPORTED",
|
|
11
|
+
"SSE_EVENT_UNDECLARED",
|
|
12
|
+
"STREAM_EVENT_TOO_LARGE",
|
|
13
|
+
"STREAM_CHUNK_TOO_LARGE",
|
|
14
|
+
"SSE_RESULT_UNSUPPORTED",
|
|
15
|
+
"STREAM_RESULT_UNSUPPORTED",
|
|
16
|
+
"AUTH_FLOW_NOT_CONFIGURED",
|
|
17
|
+
"refresh_not_supported",
|
|
18
|
+
"RUNTIME_UNSUPPORTED",
|
|
19
|
+
"PROVIDER_STATE_UNSUPPORTED",
|
|
20
|
+
"CHOICE_TOKEN_MASTER_SECRET_NOT_CONFIGURED",
|
|
21
|
+
"CHOICE_STATE_PAYLOAD_TOO_LARGE",
|
|
22
|
+
"CHOICE_STATE_UNAVAILABLE",
|
|
23
|
+
"CHOICE_CONTEXT_REQUIRED",
|
|
24
|
+
"unsupported_stealth_cookie_store_version",
|
|
25
|
+
"provider_secret_error",
|
|
26
|
+
"credential_key_error",
|
|
27
|
+
"credential_mode_error",
|
|
28
|
+
"flow_expired",
|
|
29
|
+
"turn_validation_error",
|
|
30
|
+
"context_access_error",
|
|
31
|
+
"UNSUPPORTED_STT_OPTION",
|
|
32
|
+
"INVALID_STT_AUDIO",
|
|
33
|
+
"STT_AUDIO_TOO_LARGE",
|
|
34
|
+
"STT_UPSTREAM_FAILED",
|
|
35
|
+
"INVALID_STT_VERIFICATION_CODE_OPTIONS",
|
|
36
|
+
"NO_CODE_FOUND",
|
|
37
|
+
"AMBIGUOUS_CODE",
|
|
38
|
+
"retry_invalid_policy",
|
|
39
|
+
"retry_unsafe_method",
|
|
40
|
+
"stealth_cookie_store_serialize_failed",
|
|
41
|
+
"response_too_large",
|
|
42
|
+
"transport_stream_unavailable",
|
|
43
|
+
"transport_invalid_method",
|
|
44
|
+
"http_transport_override_unsupported",
|
|
45
|
+
"http_redirect_policy_invalid",
|
|
46
|
+
"http_redirect_stopped",
|
|
47
|
+
"http_redirect_max_hops",
|
|
48
|
+
"http_redirect_missing_location",
|
|
49
|
+
"http_redirect_loop",
|
|
50
|
+
"transport_invalid_url",
|
|
51
|
+
"retry_exhausted",
|
|
52
|
+
"auth_abort_unsafe_data",
|
|
53
|
+
"credentials_auth_missing_credential_keys",
|
|
54
|
+
"credentials_auth_missing_credential",
|
|
55
|
+
"credentials_auth_invalid_login_result",
|
|
56
|
+
"credentials_auth_unknown_challenge",
|
|
57
|
+
"credentials_auth_unknown_pending_challenge",
|
|
58
|
+
"STATEFUL_FORWARDING_NOT_CONFIGURED",
|
|
59
|
+
"STATEFUL_FORWARDING_SIGNATURE_MISSING",
|
|
60
|
+
"STATEFUL_FORWARDING_NONCE_INVALID",
|
|
61
|
+
"STATEFUL_FORWARDING_TIMESTAMP_INVALID",
|
|
62
|
+
"STATEFUL_FORWARDING_SIGNATURE_INVALID",
|
|
63
|
+
"STATEFUL_FORWARDING_REPLAY_DETECTED",
|
|
64
|
+
"STATEFUL_FORWARDING_REPLAY_CACHE_FULL",
|
|
65
|
+
"STATEFUL_FORWARDING_ENVELOPE_INVALID",
|
|
66
|
+
"STATEFUL_FORWARDING_PROVIDER_MISMATCH",
|
|
67
|
+
"STATEFUL_FORWARDING_SOURCE_POD_MISMATCH",
|
|
68
|
+
"STATEFUL_FORWARDING_OWNER_FENCE_INVALID",
|
|
69
|
+
"STATEFUL_FORWARDING_REQUEST_FAILED",
|
|
70
|
+
"STATEFUL_FORWARDING_CONTEXT_MISSING",
|
|
71
|
+
"STATEFUL_FORWARDING_BAD_RESPONSE",
|
|
72
|
+
"STATEFUL_INTERNAL_EXECUTOR_NOT_CONFIGURED",
|
|
73
|
+
"STATEFUL_FILE_FORWARDING_UNSUPPORTED",
|
|
74
|
+
"STATEFUL_CONTROL_PLANE_OPERATION_AMBIGUOUS",
|
|
75
|
+
"STATEFUL_CONTROL_PLANE_REQUEST_FAILED",
|
|
76
|
+
"STATEFUL_CONTROL_PLANE_HTTP_ERROR",
|
|
77
|
+
"STATEFUL_CONTROL_PLANE_INVALID_RESPONSE",
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
// Complete code authority for provider-declared runtime resolution. Keep this
|
|
81
|
+
// separate from signal suppression: declarations may document these codes, but
|
|
82
|
+
// their status and retryability can never override the SDK's canonical result.
|
|
83
|
+
export const SDK_RUNTIME_OWNED_ERROR_CODES = new Set([
|
|
84
|
+
...SDK_OWNED_PROVIDER_ERROR_CODES,
|
|
85
|
+
"reauth_required",
|
|
86
|
+
"STT_UNAVAILABLE",
|
|
87
|
+
"UNSUPPORTED_STT_BACKEND",
|
|
88
|
+
"OUTPUT_VALIDATION_FAILED",
|
|
89
|
+
"NOT_FOUND",
|
|
90
|
+
"not_found",
|
|
91
|
+
]);
|
package/src/index.ts
CHANGED
package/src/provider.ts
CHANGED
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,
|
|
@@ -79,8 +83,10 @@ import type {
|
|
|
79
83
|
FlowContextStore,
|
|
80
84
|
HttpRetrySummary,
|
|
81
85
|
OperationDefinition,
|
|
86
|
+
OperationErrorCode,
|
|
82
87
|
OperationHttpStreamTransport,
|
|
83
88
|
OperationSseTransport,
|
|
89
|
+
ProviderErrorStatus,
|
|
84
90
|
ProviderContext,
|
|
85
91
|
ProviderDefinition,
|
|
86
92
|
ProviderProxyPolicy,
|
|
@@ -89,6 +95,7 @@ import type {
|
|
|
89
95
|
StealthClient,
|
|
90
96
|
SttContext,
|
|
91
97
|
} from "../types.js";
|
|
98
|
+
import { VALID_OPERATION_ERROR_STATUSES } from "../types.js";
|
|
92
99
|
import {
|
|
93
100
|
createSelfTestApp,
|
|
94
101
|
createSelfTestAuthFlowInvoke,
|
|
@@ -644,8 +651,12 @@ function zodDetails(error: z.ZodError): Array<{
|
|
|
644
651
|
}));
|
|
645
652
|
}
|
|
646
653
|
|
|
647
|
-
function toErrorResponse(
|
|
648
|
-
|
|
654
|
+
function toErrorResponse(
|
|
655
|
+
error: unknown,
|
|
656
|
+
requestId?: string,
|
|
657
|
+
declaredErrorCode?: OperationErrorCode,
|
|
658
|
+
): OperationErrorResponse {
|
|
659
|
+
const observability = errorObservabilityDetails(error, declaredErrorCode);
|
|
649
660
|
if (error instanceof StatefulRoutingDeadlineError) {
|
|
650
661
|
return {
|
|
651
662
|
error: {
|
|
@@ -709,7 +720,13 @@ function toErrorResponse(error: unknown, requestId?: string): OperationErrorResp
|
|
|
709
720
|
// narrowing from a ProviderError-typed value would collapse the negative branch
|
|
710
721
|
// to `never`. Narrowing from unknown avoids that while still recognizing errors
|
|
711
722
|
// from a duplicate SDK module instance.
|
|
712
|
-
function providerObservabilityDetails(
|
|
723
|
+
function providerObservabilityDetails(
|
|
724
|
+
error: unknown,
|
|
725
|
+
declaredErrorCode?: OperationErrorCode,
|
|
726
|
+
): ErrorObservabilityDetails | undefined {
|
|
727
|
+
const declaredRetryable = sdkOwnsErrorResolution(error)
|
|
728
|
+
? undefined
|
|
729
|
+
: declaredErrorCode?.retryable;
|
|
713
730
|
// Session-expiry surfaces the credential_expired category + the opt-in
|
|
714
731
|
// retryable signal so Gateway/Credential Service can refresh and re-drive the
|
|
715
732
|
// operation (see design.md §4.3 D3). Without this branch the auth error would
|
|
@@ -719,7 +736,7 @@ function providerObservabilityDetails(error: unknown): ErrorObservabilityDetails
|
|
|
719
736
|
return {
|
|
720
737
|
category: error.options?.category ?? "credential_expired",
|
|
721
738
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
722
|
-
retryable: error.options?.retryable ?? false,
|
|
739
|
+
retryable: error.options?.retryable ?? declaredRetryable ?? false,
|
|
723
740
|
};
|
|
724
741
|
}
|
|
725
742
|
// Missing-secret errors carry the canonical credential_unavailable category
|
|
@@ -731,7 +748,7 @@ function providerObservabilityDetails(error: unknown): ErrorObservabilityDetails
|
|
|
731
748
|
return {
|
|
732
749
|
category: error.options?.category ?? "credential_unavailable",
|
|
733
750
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
734
|
-
retryable: error.options?.retryable ?? false,
|
|
751
|
+
retryable: error.options?.retryable ?? declaredRetryable ?? false,
|
|
735
752
|
};
|
|
736
753
|
}
|
|
737
754
|
if (!isTransportError(error)) {
|
|
@@ -766,18 +783,27 @@ function providerObservabilityDetails(error: unknown): ErrorObservabilityDetails
|
|
|
766
783
|
};
|
|
767
784
|
}
|
|
768
785
|
|
|
769
|
-
function errorObservabilityDetails(
|
|
770
|
-
|
|
786
|
+
function errorObservabilityDetails(
|
|
787
|
+
error: unknown,
|
|
788
|
+
declaredErrorCode?: OperationErrorCode,
|
|
789
|
+
): ErrorObservabilityDetails {
|
|
790
|
+
const effectiveDeclaration = sdkOwnsErrorResolution(error) ? undefined : declaredErrorCode;
|
|
791
|
+
const providerDetails = providerObservabilityDetails(error, effectiveDeclaration);
|
|
771
792
|
if (providerDetails) return providerDetails;
|
|
772
793
|
|
|
773
794
|
if (error instanceof z.ZodError || isValidationError(error)) {
|
|
795
|
+
const declaredStatus = effectiveDeclaration?.status;
|
|
774
796
|
return {
|
|
775
797
|
category:
|
|
776
798
|
isProviderError(error) && error.options?.category
|
|
777
799
|
? error.options.category
|
|
778
|
-
:
|
|
800
|
+
: isEmittableErrorStatus(declaredStatus) && declaredStatus >= 500
|
|
801
|
+
? "provider_error"
|
|
802
|
+
: "input_validation",
|
|
779
803
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
780
|
-
retryable: isProviderError(error)
|
|
804
|
+
retryable: isProviderError(error)
|
|
805
|
+
? (error.options?.retryable ?? effectiveDeclaration?.retryable ?? false)
|
|
806
|
+
: false,
|
|
781
807
|
};
|
|
782
808
|
}
|
|
783
809
|
|
|
@@ -793,7 +819,7 @@ function errorObservabilityDetails(error: unknown): ErrorObservabilityDetails {
|
|
|
793
819
|
return {
|
|
794
820
|
category: error.options?.category ?? "provider_error",
|
|
795
821
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
796
|
-
retryable: error.options?.retryable ?? false,
|
|
822
|
+
retryable: error.options?.retryable ?? effectiveDeclaration?.retryable ?? false,
|
|
797
823
|
};
|
|
798
824
|
}
|
|
799
825
|
|
|
@@ -804,9 +830,16 @@ function errorObservabilityDetails(error: unknown): ErrorObservabilityDetails {
|
|
|
804
830
|
};
|
|
805
831
|
}
|
|
806
832
|
|
|
807
|
-
function responseWithErrorObservability(
|
|
833
|
+
function responseWithErrorObservability(
|
|
834
|
+
response: Response,
|
|
835
|
+
error: unknown,
|
|
836
|
+
declaredErrorCode?: OperationErrorCode,
|
|
837
|
+
): Response {
|
|
808
838
|
const headers = new Headers(response.headers);
|
|
809
|
-
headers.set(
|
|
839
|
+
headers.set(
|
|
840
|
+
ERROR_OBSERVABILITY_HEADER,
|
|
841
|
+
JSON.stringify(errorObservabilityDetails(error, declaredErrorCode)),
|
|
842
|
+
);
|
|
810
843
|
return new Response(response.body, {
|
|
811
844
|
status: response.status,
|
|
812
845
|
statusText: response.statusText,
|
|
@@ -838,7 +871,14 @@ function publicProviderErrorMessage(error: ProviderError): string {
|
|
|
838
871
|
return error.message;
|
|
839
872
|
}
|
|
840
873
|
|
|
841
|
-
function
|
|
874
|
+
function isEmittableErrorStatus(value: unknown): value is ProviderErrorStatus {
|
|
875
|
+
return (
|
|
876
|
+
typeof value === "number" &&
|
|
877
|
+
VALID_OPERATION_ERROR_STATUSES.some((status) => status === value)
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function toStatusCode(error: unknown, declaredErrorCode?: OperationErrorCode): ProviderErrorStatus {
|
|
842
882
|
if (error instanceof z.ZodError) {
|
|
843
883
|
return 400;
|
|
844
884
|
}
|
|
@@ -846,6 +886,12 @@ function toStatusCode(error: unknown): 400 | 401 | 404 | 429 | 500 | 502 | 503 |
|
|
|
846
886
|
return 504;
|
|
847
887
|
}
|
|
848
888
|
if (isProviderError(error)) {
|
|
889
|
+
if (
|
|
890
|
+
!sdkOwnsErrorResolution(error) &&
|
|
891
|
+
isEmittableErrorStatus(declaredErrorCode?.status)
|
|
892
|
+
) {
|
|
893
|
+
return declaredErrorCode.status;
|
|
894
|
+
}
|
|
849
895
|
switch (error.code) {
|
|
850
896
|
case "AUTH_REQUIRED":
|
|
851
897
|
case "reauth_required":
|
|
@@ -883,82 +929,39 @@ function toStatusCode(error: unknown): 400 | 401 | 404 | 429 | 500 | 502 | 503 |
|
|
|
883
929
|
return 500;
|
|
884
930
|
}
|
|
885
931
|
|
|
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
|
-
]);
|
|
932
|
+
function sdkOwnsErrorResolution(error: unknown): boolean {
|
|
933
|
+
if (isSessionExpiredError(error)) return true;
|
|
934
|
+
if (isTransportError(error)) return true;
|
|
935
|
+
if (error instanceof z.ZodError) return true;
|
|
936
|
+
if (error instanceof StatefulRoutingDeadlineError) return true;
|
|
937
|
+
return (
|
|
938
|
+
isProviderError(error) &&
|
|
939
|
+
typeof error.code === "string" &&
|
|
940
|
+
SDK_RUNTIME_OWNED_ERROR_CODES.has(error.code)
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
type OperationErrorCodeLookup = ReadonlyMap<string, ReadonlyMap<string, OperationErrorCode>>;
|
|
945
|
+
|
|
946
|
+
function buildOperationErrorCodeLookup(provider: ProviderDefinition): OperationErrorCodeLookup {
|
|
947
|
+
return new Map(
|
|
948
|
+
Object.entries(provider.operations).flatMap(([operationId, operation]) => {
|
|
949
|
+
const errorCodes = operation.docs?.errorCodes;
|
|
950
|
+
return errorCodes?.length
|
|
951
|
+
? [[operationId, new Map(errorCodes.map((entry) => [entry.code, entry]))] as const]
|
|
952
|
+
: [];
|
|
953
|
+
}),
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
function declaredErrorCodeFor(
|
|
958
|
+
error: unknown,
|
|
959
|
+
operationId: string | undefined,
|
|
960
|
+
lookup: OperationErrorCodeLookup,
|
|
961
|
+
): OperationErrorCode | undefined {
|
|
962
|
+
if (!operationId || !isProviderError(error) || typeof error.code !== "string") return undefined;
|
|
963
|
+
return lookup.get(operationId)?.get(error.code);
|
|
964
|
+
}
|
|
962
965
|
|
|
963
966
|
function extractRequestId(raw: unknown): string | undefined {
|
|
964
967
|
if (!raw || typeof raw !== "object") {
|
|
@@ -978,6 +981,7 @@ function logProviderError(
|
|
|
978
981
|
error: unknown,
|
|
979
982
|
status: number,
|
|
980
983
|
cost: ProviderRequestCost,
|
|
984
|
+
declaredErrorCode?: OperationErrorCode,
|
|
981
985
|
): void {
|
|
982
986
|
const code = isProviderError(error)
|
|
983
987
|
? (error.code ?? "provider_error")
|
|
@@ -988,13 +992,14 @@ function logProviderError(
|
|
|
988
992
|
: "internal_error";
|
|
989
993
|
const errorClass = error instanceof Error ? error.name : typeof error;
|
|
990
994
|
const message = error instanceof Error ? error.message : String(error);
|
|
991
|
-
const details = errorObservabilityDetails(error);
|
|
995
|
+
const details = errorObservabilityDetails(error, declaredErrorCode);
|
|
992
996
|
const isUnregisteredProviderErrorCode =
|
|
993
997
|
status === 500 &&
|
|
994
998
|
isProviderError(error) &&
|
|
995
999
|
!isValidationError(error) &&
|
|
996
1000
|
typeof error.code === "string" &&
|
|
997
|
-
!SDK_OWNED_PROVIDER_ERROR_CODES.has(error.code)
|
|
1001
|
+
!SDK_OWNED_PROVIDER_ERROR_CODES.has(error.code) &&
|
|
1002
|
+
declaredErrorCode === undefined;
|
|
998
1003
|
const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
|
|
999
1004
|
emit({
|
|
1000
1005
|
level: status >= 500 ? "error" : "warn",
|
|
@@ -1729,6 +1734,7 @@ export function createServerApp(
|
|
|
1729
1734
|
validateStatefulServerConfig(options);
|
|
1730
1735
|
const app = new Hono();
|
|
1731
1736
|
const logger = options.logger ?? defaultProviderServerLogger;
|
|
1737
|
+
const operationErrorCodes = buildOperationErrorCodeLookup(provider);
|
|
1732
1738
|
const statefulForwardingReplayCache = new StatefulForwardingReplayCache(
|
|
1733
1739
|
options.statefulForwarding?.replayCacheMaxEntries ??
|
|
1734
1740
|
DEFAULT_STATEFUL_FORWARDING_REPLAY_CACHE_MAX_ENTRIES,
|
|
@@ -1774,6 +1780,7 @@ export function createServerApp(
|
|
|
1774
1780
|
app.post(STATEFUL_INTERNAL_OPERATIONS_ROUTE, async (c) => {
|
|
1775
1781
|
let rawBodyText = "";
|
|
1776
1782
|
let rawBody: unknown;
|
|
1783
|
+
let operationId: string | undefined;
|
|
1777
1784
|
const operation = "stateful-internal";
|
|
1778
1785
|
const requestCost = startRequestCost();
|
|
1779
1786
|
try {
|
|
@@ -1884,7 +1891,7 @@ export function createServerApp(
|
|
|
1884
1891
|
});
|
|
1885
1892
|
}
|
|
1886
1893
|
const request = operationRequestFromForwardingEnvelope(envelope);
|
|
1887
|
-
|
|
1894
|
+
operationId = envelope.operationId;
|
|
1888
1895
|
const ctx = createProviderContext(provider, request, operationId, options, state);
|
|
1889
1896
|
if (deadlineAtMs !== undefined && deadlineAtMs <= Date.now()) {
|
|
1890
1897
|
throw new StatefulRoutingDeadlineError(envelope.requestId, envelope.deadlineAt as string);
|
|
@@ -1908,7 +1915,8 @@ export function createServerApp(
|
|
|
1908
1915
|
);
|
|
1909
1916
|
return c.json({ data: output });
|
|
1910
1917
|
} catch (error) {
|
|
1911
|
-
const
|
|
1918
|
+
const declaredErrorCode = declaredErrorCodeFor(error, operationId, operationErrorCodes);
|
|
1919
|
+
const status = toStatusCode(error, declaredErrorCode);
|
|
1912
1920
|
if (isProviderError(error) && error.code === "STATEFUL_FORWARDING_REPLAY_CACHE_FULL") {
|
|
1913
1921
|
c.header("Retry-After", String(STATEFUL_FORWARDING_REPLAY_RETRY_AFTER_SECONDS));
|
|
1914
1922
|
}
|
|
@@ -1917,15 +1925,17 @@ export function createServerApp(
|
|
|
1917
1925
|
logger,
|
|
1918
1926
|
provider,
|
|
1919
1927
|
"operation",
|
|
1920
|
-
operation,
|
|
1928
|
+
operationId || operation,
|
|
1921
1929
|
requestId,
|
|
1922
1930
|
error,
|
|
1923
1931
|
status,
|
|
1924
1932
|
finishRequestCost(requestCost),
|
|
1933
|
+
declaredErrorCode,
|
|
1925
1934
|
);
|
|
1926
1935
|
return responseWithErrorObservability(
|
|
1927
|
-
c.json(toErrorResponse(error, requestId), status),
|
|
1936
|
+
c.json(toErrorResponse(error, requestId, declaredErrorCode), status),
|
|
1928
1937
|
error,
|
|
1938
|
+
declaredErrorCode,
|
|
1929
1939
|
);
|
|
1930
1940
|
}
|
|
1931
1941
|
});
|
|
@@ -1976,7 +1986,8 @@ export function createServerApp(
|
|
|
1976
1986
|
);
|
|
1977
1987
|
return c.json(response);
|
|
1978
1988
|
} catch (error) {
|
|
1979
|
-
const
|
|
1989
|
+
const declaredErrorCode = declaredErrorCodeFor(error, operation, operationErrorCodes);
|
|
1990
|
+
const status = toStatusCode(error, declaredErrorCode);
|
|
1980
1991
|
const requestId = extractRequestId(rawBody);
|
|
1981
1992
|
logProviderError(
|
|
1982
1993
|
logger,
|
|
@@ -1987,12 +1998,14 @@ export function createServerApp(
|
|
|
1987
1998
|
error,
|
|
1988
1999
|
status,
|
|
1989
2000
|
finishRequestCost(requestCost),
|
|
2001
|
+
declaredErrorCode,
|
|
1990
2002
|
);
|
|
1991
2003
|
const telemetryHeader = proxyTelemetry.toHeaderValue();
|
|
1992
2004
|
if (telemetryHeader) c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
|
|
1993
2005
|
return responseWithErrorObservability(
|
|
1994
|
-
c.json(toErrorResponse(error, requestId), status),
|
|
2006
|
+
c.json(toErrorResponse(error, requestId, declaredErrorCode), status),
|
|
1995
2007
|
error,
|
|
2008
|
+
declaredErrorCode,
|
|
1996
2009
|
);
|
|
1997
2010
|
}
|
|
1998
2011
|
});
|
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
|
}
|