@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
|
@@ -3,6 +3,11 @@ export declare const NODEMAVEN_USERNAME_ENV = "APIFUSE__PROXY__NODEMAVEN_USERNAM
|
|
|
3
3
|
export declare const NODEMAVEN_PASSWORD_ENV = "APIFUSE__PROXY__NODEMAVEN_PASSWORD";
|
|
4
4
|
export declare const NODEMAVEN_FILTER_ENV = "APIFUSE__PROXY__NODEMAVEN_FILTER";
|
|
5
5
|
export declare const NODEMAVEN_GATEWAY_HOST = "gate.nodemaven.com";
|
|
6
|
+
export type NodemavenCredentials = {
|
|
7
|
+
readonly username: string;
|
|
8
|
+
readonly password: string;
|
|
9
|
+
readonly filter?: string;
|
|
10
|
+
};
|
|
6
11
|
/** Both schemes tunnel bytes end-to-end, preserving the client TLS handshake. */
|
|
7
12
|
export type ProxyProtocol = "http" | "socks5";
|
|
8
13
|
/**
|
|
@@ -23,6 +28,8 @@ export type NodemavenSessionWindow = {
|
|
|
23
28
|
export declare function nodemavenSessionWindow(policy: ProviderProxyPolicy, now?: number): NodemavenSessionWindow;
|
|
24
29
|
export type NodemavenSynthesisInput = {
|
|
25
30
|
policy: ProviderProxyPolicy;
|
|
31
|
+
/** Explicit credentials; synthesis never reads process-global state. */
|
|
32
|
+
credentials: NodemavenCredentials;
|
|
26
33
|
affinityKey: string | undefined;
|
|
27
34
|
protocol: ProxyProtocol;
|
|
28
35
|
poolIndex: number;
|
|
@@ -30,8 +30,8 @@ function readNodemavenUsername() {
|
|
|
30
30
|
function readNodemavenPassword() {
|
|
31
31
|
return process.env[NODEMAVEN_PASSWORD_ENV]?.trim() || undefined;
|
|
32
32
|
}
|
|
33
|
-
function resolveNodemavenFilter() {
|
|
34
|
-
const raw =
|
|
33
|
+
function resolveNodemavenFilter(value) {
|
|
34
|
+
const raw = value?.trim().toLowerCase();
|
|
35
35
|
if (!raw)
|
|
36
36
|
return DEFAULT_NODEMAVEN_FILTER;
|
|
37
37
|
if (!NODEMAVEN_FILTERS.has(raw)) {
|
|
@@ -97,12 +97,12 @@ function selectPort(protocol, sid, poolIndex) {
|
|
|
97
97
|
* There is no allocation API — geo/session are encoded in the username.
|
|
98
98
|
*/
|
|
99
99
|
export function synthesizeNodemavenProxy(input) {
|
|
100
|
-
const username =
|
|
101
|
-
const password =
|
|
100
|
+
const username = input.credentials.username.trim();
|
|
101
|
+
const password = input.credentials.password.trim();
|
|
102
102
|
if (!username || !password) {
|
|
103
103
|
throw new Error(`NodeMaven credentials missing: set ${NODEMAVEN_USERNAME_ENV} and ${NODEMAVEN_PASSWORD_ENV}.`);
|
|
104
104
|
}
|
|
105
|
-
const filter = resolveNodemavenFilter();
|
|
105
|
+
const filter = resolveNodemavenFilter(input.credentials.filter);
|
|
106
106
|
const sid = deriveSid(input.policy, input.affinityKey, input.poolIndex, input.refreshEpoch);
|
|
107
107
|
const port = selectPort(input.protocol, sid, input.poolIndex);
|
|
108
108
|
const lifetimeMinutes = nodemavenLifetimeMinutes(input.policy);
|
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";
|
|
@@ -15,7 +16,7 @@ import { createEnvContext } from "../runtime/env.js";
|
|
|
15
16
|
import { executeOperation } from "../runtime/executor.js";
|
|
16
17
|
import { createHttpClient } from "../runtime/http.js";
|
|
17
18
|
import { wrapWithInstrumentation } from "../runtime/instrumentation.js";
|
|
18
|
-
import { createNativeNetworkClient } from "../runtime/native-network.js";
|
|
19
|
+
import { createEnvVendorCredentialResolver, createNativeNetworkClient, } from "../runtime/native-network.js";
|
|
19
20
|
import { getProviderBaseUrl } from "../runtime/provider.js";
|
|
20
21
|
import { PROXY_AUTH_IP_DENIED_CODE, PROXY_EDGE_AUTH_REJECTED_CODE, PROXY_POOL_EXHAUSTED_CODE, } from "../runtime/proxy-errors.js";
|
|
21
22
|
import { PROVIDER_TELEMETRY_HEADER, ProxyTelemetryCollector } from "../runtime/proxy-telemetry.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";
|
|
@@ -223,6 +225,7 @@ function createProviderContext(provider, request, operationId, options = {}, sta
|
|
|
223
225
|
egress: provider.native.network,
|
|
224
226
|
proxyPolicy: resolveNativeProxyPolicy(provider),
|
|
225
227
|
affinityKey: proxyClientOptions.affinityKey,
|
|
228
|
+
credentials: createEnvVendorCredentialResolver(env),
|
|
226
229
|
}),
|
|
227
230
|
},
|
|
228
231
|
}
|
|
@@ -310,6 +313,7 @@ function createAuthFlowContext(provider, request, options = {}, signal) {
|
|
|
310
313
|
egress: provider.native.network,
|
|
311
314
|
proxyPolicy: resolveNativeProxyPolicy(provider),
|
|
312
315
|
affinityKey: proxyClientOptions.affinityKey,
|
|
316
|
+
credentials: createEnvVendorCredentialResolver(createEnvContext(provider.secrets?.map((secret) => secret.name))),
|
|
313
317
|
}),
|
|
314
318
|
},
|
|
315
319
|
}
|
|
@@ -353,8 +357,8 @@ function zodDetails(error) {
|
|
|
353
357
|
message: issue.message,
|
|
354
358
|
}));
|
|
355
359
|
}
|
|
356
|
-
function toErrorResponse(error, requestId) {
|
|
357
|
-
const observability = errorObservabilityDetails(error);
|
|
360
|
+
function toErrorResponse(error, requestId, declaredErrorCode) {
|
|
361
|
+
const observability = errorObservabilityDetails(error, declaredErrorCode);
|
|
358
362
|
if (error instanceof StatefulRoutingDeadlineError) {
|
|
359
363
|
return {
|
|
360
364
|
error: {
|
|
@@ -414,7 +418,10 @@ function toErrorResponse(error, requestId) {
|
|
|
414
418
|
// narrowing from a ProviderError-typed value would collapse the negative branch
|
|
415
419
|
// to `never`. Narrowing from unknown avoids that while still recognizing errors
|
|
416
420
|
// from a duplicate SDK module instance.
|
|
417
|
-
function providerObservabilityDetails(error) {
|
|
421
|
+
function providerObservabilityDetails(error, declaredErrorCode) {
|
|
422
|
+
const declaredRetryable = sdkOwnsErrorResolution(error)
|
|
423
|
+
? undefined
|
|
424
|
+
: declaredErrorCode?.retryable;
|
|
418
425
|
// Session-expiry surfaces the credential_expired category + the opt-in
|
|
419
426
|
// retryable signal so Gateway/Credential Service can refresh and re-drive the
|
|
420
427
|
// operation (see design.md §4.3 D3). Without this branch the auth error would
|
|
@@ -424,7 +431,7 @@ function providerObservabilityDetails(error) {
|
|
|
424
431
|
return {
|
|
425
432
|
category: error.options?.category ?? "credential_expired",
|
|
426
433
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
427
|
-
retryable: error.options?.retryable ?? false,
|
|
434
|
+
retryable: error.options?.retryable ?? declaredRetryable ?? false,
|
|
428
435
|
};
|
|
429
436
|
}
|
|
430
437
|
// Missing-secret errors carry the canonical credential_unavailable category
|
|
@@ -436,7 +443,7 @@ function providerObservabilityDetails(error) {
|
|
|
436
443
|
return {
|
|
437
444
|
category: error.options?.category ?? "credential_unavailable",
|
|
438
445
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
439
|
-
retryable: error.options?.retryable ?? false,
|
|
446
|
+
retryable: error.options?.retryable ?? declaredRetryable ?? false,
|
|
440
447
|
};
|
|
441
448
|
}
|
|
442
449
|
if (!isTransportError(error)) {
|
|
@@ -467,17 +474,23 @@ function providerObservabilityDetails(error) {
|
|
|
467
474
|
...(error.upstreamStatus ? { upstreamStatus: error.upstreamStatus } : {}),
|
|
468
475
|
};
|
|
469
476
|
}
|
|
470
|
-
function errorObservabilityDetails(error) {
|
|
471
|
-
const
|
|
477
|
+
function errorObservabilityDetails(error, declaredErrorCode) {
|
|
478
|
+
const effectiveDeclaration = sdkOwnsErrorResolution(error) ? undefined : declaredErrorCode;
|
|
479
|
+
const providerDetails = providerObservabilityDetails(error, effectiveDeclaration);
|
|
472
480
|
if (providerDetails)
|
|
473
481
|
return providerDetails;
|
|
474
482
|
if (error instanceof z.ZodError || isValidationError(error)) {
|
|
483
|
+
const declaredStatus = effectiveDeclaration?.status;
|
|
475
484
|
return {
|
|
476
485
|
category: isProviderError(error) && error.options?.category
|
|
477
486
|
? error.options.category
|
|
478
|
-
:
|
|
487
|
+
: isEmittableErrorStatus(declaredStatus) && declaredStatus >= 500
|
|
488
|
+
? "provider_error"
|
|
489
|
+
: "input_validation",
|
|
479
490
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
480
|
-
retryable: isProviderError(error)
|
|
491
|
+
retryable: isProviderError(error)
|
|
492
|
+
? (error.options?.retryable ?? effectiveDeclaration?.retryable ?? false)
|
|
493
|
+
: false,
|
|
481
494
|
};
|
|
482
495
|
}
|
|
483
496
|
if (error instanceof StatefulRoutingDeadlineError) {
|
|
@@ -491,7 +504,7 @@ function errorObservabilityDetails(error) {
|
|
|
491
504
|
return {
|
|
492
505
|
category: error.options?.category ?? "provider_error",
|
|
493
506
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
494
|
-
retryable: error.options?.retryable ?? false,
|
|
507
|
+
retryable: error.options?.retryable ?? effectiveDeclaration?.retryable ?? false,
|
|
495
508
|
};
|
|
496
509
|
}
|
|
497
510
|
return {
|
|
@@ -500,9 +513,9 @@ function errorObservabilityDetails(error) {
|
|
|
500
513
|
retryable: false,
|
|
501
514
|
};
|
|
502
515
|
}
|
|
503
|
-
function responseWithErrorObservability(response, error) {
|
|
516
|
+
function responseWithErrorObservability(response, error, declaredErrorCode) {
|
|
504
517
|
const headers = new Headers(response.headers);
|
|
505
|
-
headers.set(ERROR_OBSERVABILITY_HEADER, JSON.stringify(errorObservabilityDetails(error)));
|
|
518
|
+
headers.set(ERROR_OBSERVABILITY_HEADER, JSON.stringify(errorObservabilityDetails(error, declaredErrorCode)));
|
|
506
519
|
return new Response(response.body, {
|
|
507
520
|
status: response.status,
|
|
508
521
|
statusText: response.statusText,
|
|
@@ -534,7 +547,11 @@ function publicProviderErrorMessage(error) {
|
|
|
534
547
|
}
|
|
535
548
|
return error.message;
|
|
536
549
|
}
|
|
537
|
-
function
|
|
550
|
+
function isEmittableErrorStatus(value) {
|
|
551
|
+
return (typeof value === "number" &&
|
|
552
|
+
VALID_OPERATION_ERROR_STATUSES.some((status) => status === value));
|
|
553
|
+
}
|
|
554
|
+
function toStatusCode(error, declaredErrorCode) {
|
|
538
555
|
if (error instanceof z.ZodError) {
|
|
539
556
|
return 400;
|
|
540
557
|
}
|
|
@@ -542,6 +559,10 @@ function toStatusCode(error) {
|
|
|
542
559
|
return 504;
|
|
543
560
|
}
|
|
544
561
|
if (isProviderError(error)) {
|
|
562
|
+
if (!sdkOwnsErrorResolution(error) &&
|
|
563
|
+
isEmittableErrorStatus(declaredErrorCode?.status)) {
|
|
564
|
+
return declaredErrorCode.status;
|
|
565
|
+
}
|
|
545
566
|
switch (error.code) {
|
|
546
567
|
case "AUTH_REQUIRED":
|
|
547
568
|
case "reauth_required":
|
|
@@ -576,82 +597,32 @@ function toStatusCode(error) {
|
|
|
576
597
|
}
|
|
577
598
|
return 500;
|
|
578
599
|
}
|
|
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
|
-
]);
|
|
600
|
+
function sdkOwnsErrorResolution(error) {
|
|
601
|
+
if (isSessionExpiredError(error))
|
|
602
|
+
return true;
|
|
603
|
+
if (isTransportError(error))
|
|
604
|
+
return true;
|
|
605
|
+
if (error instanceof z.ZodError)
|
|
606
|
+
return true;
|
|
607
|
+
if (error instanceof StatefulRoutingDeadlineError)
|
|
608
|
+
return true;
|
|
609
|
+
return (isProviderError(error) &&
|
|
610
|
+
typeof error.code === "string" &&
|
|
611
|
+
SDK_RUNTIME_OWNED_ERROR_CODES.has(error.code));
|
|
612
|
+
}
|
|
613
|
+
function buildOperationErrorCodeLookup(provider) {
|
|
614
|
+
return new Map(Object.entries(provider.operations).flatMap(([operationId, operation]) => {
|
|
615
|
+
const errorCodes = operation.docs?.errorCodes;
|
|
616
|
+
return errorCodes?.length
|
|
617
|
+
? [[operationId, new Map(errorCodes.map((entry) => [entry.code, entry]))]]
|
|
618
|
+
: [];
|
|
619
|
+
}));
|
|
620
|
+
}
|
|
621
|
+
function declaredErrorCodeFor(error, operationId, lookup) {
|
|
622
|
+
if (!operationId || !isProviderError(error) || typeof error.code !== "string")
|
|
623
|
+
return undefined;
|
|
624
|
+
return lookup.get(operationId)?.get(error.code);
|
|
625
|
+
}
|
|
655
626
|
function extractRequestId(raw) {
|
|
656
627
|
if (!raw || typeof raw !== "object") {
|
|
657
628
|
return undefined;
|
|
@@ -659,7 +630,7 @@ function extractRequestId(raw) {
|
|
|
659
630
|
const value = Object.getOwnPropertyDescriptor(raw, "requestId")?.value;
|
|
660
631
|
return typeof value === "string" ? value : undefined;
|
|
661
632
|
}
|
|
662
|
-
function logProviderError(logger, provider, kind, route, requestId, error, status, cost) {
|
|
633
|
+
function logProviderError(logger, provider, kind, route, requestId, error, status, cost, declaredErrorCode) {
|
|
663
634
|
const code = isProviderError(error)
|
|
664
635
|
? (error.code ?? "provider_error")
|
|
665
636
|
: error instanceof z.ZodError
|
|
@@ -669,12 +640,13 @@ function logProviderError(logger, provider, kind, route, requestId, error, statu
|
|
|
669
640
|
: "internal_error";
|
|
670
641
|
const errorClass = error instanceof Error ? error.name : typeof error;
|
|
671
642
|
const message = error instanceof Error ? error.message : String(error);
|
|
672
|
-
const details = errorObservabilityDetails(error);
|
|
643
|
+
const details = errorObservabilityDetails(error, declaredErrorCode);
|
|
673
644
|
const isUnregisteredProviderErrorCode = status === 500 &&
|
|
674
645
|
isProviderError(error) &&
|
|
675
646
|
!isValidationError(error) &&
|
|
676
647
|
typeof error.code === "string" &&
|
|
677
|
-
!SDK_OWNED_PROVIDER_ERROR_CODES.has(error.code)
|
|
648
|
+
!SDK_OWNED_PROVIDER_ERROR_CODES.has(error.code) &&
|
|
649
|
+
declaredErrorCode === undefined;
|
|
678
650
|
const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
|
|
679
651
|
emit({
|
|
680
652
|
level: status >= 500 ? "error" : "warn",
|
|
@@ -1270,6 +1242,7 @@ export function createServerApp(provider, options = {}) {
|
|
|
1270
1242
|
validateStatefulServerConfig(options);
|
|
1271
1243
|
const app = new Hono();
|
|
1272
1244
|
const logger = options.logger ?? defaultProviderServerLogger;
|
|
1245
|
+
const operationErrorCodes = buildOperationErrorCodeLookup(provider);
|
|
1273
1246
|
const statefulForwardingReplayCache = new StatefulForwardingReplayCache(options.statefulForwarding?.replayCacheMaxEntries ??
|
|
1274
1247
|
DEFAULT_STATEFUL_FORWARDING_REPLAY_CACHE_MAX_ENTRIES);
|
|
1275
1248
|
const state = options.state ??
|
|
@@ -1303,6 +1276,7 @@ export function createServerApp(provider, options = {}) {
|
|
|
1303
1276
|
app.post(STATEFUL_INTERNAL_OPERATIONS_ROUTE, async (c) => {
|
|
1304
1277
|
let rawBodyText = "";
|
|
1305
1278
|
let rawBody;
|
|
1279
|
+
let operationId;
|
|
1306
1280
|
const operation = "stateful-internal";
|
|
1307
1281
|
const requestCost = startRequestCost();
|
|
1308
1282
|
try {
|
|
@@ -1388,7 +1362,7 @@ export function createServerApp(provider, options = {}) {
|
|
|
1388
1362
|
});
|
|
1389
1363
|
}
|
|
1390
1364
|
const request = operationRequestFromForwardingEnvelope(envelope);
|
|
1391
|
-
|
|
1365
|
+
operationId = envelope.operationId;
|
|
1392
1366
|
const ctx = createProviderContext(provider, request, operationId, options, state);
|
|
1393
1367
|
if (deadlineAtMs !== undefined && deadlineAtMs <= Date.now()) {
|
|
1394
1368
|
throw new StatefulRoutingDeadlineError(envelope.requestId, envelope.deadlineAt);
|
|
@@ -1405,13 +1379,14 @@ export function createServerApp(provider, options = {}) {
|
|
|
1405
1379
|
return c.json({ data: output });
|
|
1406
1380
|
}
|
|
1407
1381
|
catch (error) {
|
|
1408
|
-
const
|
|
1382
|
+
const declaredErrorCode = declaredErrorCodeFor(error, operationId, operationErrorCodes);
|
|
1383
|
+
const status = toStatusCode(error, declaredErrorCode);
|
|
1409
1384
|
if (isProviderError(error) && error.code === "STATEFUL_FORWARDING_REPLAY_CACHE_FULL") {
|
|
1410
1385
|
c.header("Retry-After", String(STATEFUL_FORWARDING_REPLAY_RETRY_AFTER_SECONDS));
|
|
1411
1386
|
}
|
|
1412
1387
|
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);
|
|
1388
|
+
logProviderError(logger, provider, "operation", operationId || operation, requestId, error, status, finishRequestCost(requestCost), declaredErrorCode);
|
|
1389
|
+
return responseWithErrorObservability(c.json(toErrorResponse(error, requestId, declaredErrorCode), status), error, declaredErrorCode);
|
|
1415
1390
|
}
|
|
1416
1391
|
});
|
|
1417
1392
|
app.post("/v1/:operation", async (c) => {
|
|
@@ -1439,13 +1414,14 @@ export function createServerApp(provider, options = {}) {
|
|
|
1439
1414
|
return c.json(response);
|
|
1440
1415
|
}
|
|
1441
1416
|
catch (error) {
|
|
1442
|
-
const
|
|
1417
|
+
const declaredErrorCode = declaredErrorCodeFor(error, operation, operationErrorCodes);
|
|
1418
|
+
const status = toStatusCode(error, declaredErrorCode);
|
|
1443
1419
|
const requestId = extractRequestId(rawBody);
|
|
1444
|
-
logProviderError(logger, provider, "operation", operation, requestId, error, status, finishRequestCost(requestCost));
|
|
1420
|
+
logProviderError(logger, provider, "operation", operation, requestId, error, status, finishRequestCost(requestCost), declaredErrorCode);
|
|
1445
1421
|
const telemetryHeader = proxyTelemetry.toHeaderValue();
|
|
1446
1422
|
if (telemetryHeader)
|
|
1447
1423
|
c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
|
|
1448
|
-
return responseWithErrorObservability(c.json(toErrorResponse(error, requestId), status), error);
|
|
1424
|
+
return responseWithErrorObservability(c.json(toErrorResponse(error, requestId, declaredErrorCode), status), error, declaredErrorCode);
|
|
1449
1425
|
}
|
|
1450
1426
|
});
|
|
1451
1427
|
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
|
}
|
|
@@ -1087,6 +1089,10 @@ export interface NativeTcpEgressRule {
|
|
|
1087
1089
|
/**
|
|
1088
1090
|
* Bounded native TCP egress discovered through a declared bootstrap endpoint.
|
|
1089
1091
|
* Host suffixes are exact DNS suffixes, not wildcard patterns.
|
|
1092
|
+
* Dynamic rules must declare at least one target host selector through
|
|
1093
|
+
* targetHostSuffixes and/or targetIpv4Cidrs. IPv4-literal grant targets match
|
|
1094
|
+
* only targetIpv4Cidrs, while DNS-name targets match only targetHostSuffixes.
|
|
1095
|
+
* CIDRs are exact IPv4 networks in a.b.c.d/nn form.
|
|
1090
1096
|
*
|
|
1091
1097
|
* Dynamic rules are ordered. The first rule whose source, target, port, and TLS
|
|
1092
1098
|
* selectors match exclusively owns the grant; its ttlMs and maxGrants bounds
|
|
@@ -1099,7 +1105,8 @@ export interface NativeTcpDynamicEgressRule {
|
|
|
1099
1105
|
readonly sourceHostSuffixes?: readonly string[];
|
|
1100
1106
|
readonly sourcePorts?: readonly number[];
|
|
1101
1107
|
readonly sourcePortRanges?: readonly NativeTcpPortRange[];
|
|
1102
|
-
readonly targetHostSuffixes
|
|
1108
|
+
readonly targetHostSuffixes?: readonly string[];
|
|
1109
|
+
readonly targetIpv4Cidrs?: readonly string[];
|
|
1103
1110
|
readonly targetPorts?: readonly number[];
|
|
1104
1111
|
readonly targetPortRanges?: readonly NativeTcpPortRange[];
|
|
1105
1112
|
readonly tls: NativeTcpTlsMode;
|
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