@apifuse/provider-sdk 2.2.0-beta.60 → 2.2.0-beta.62
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 +17 -16
- package/CHANGELOG.md +8 -0
- package/bin/apifuse-dev.ts +5 -7
- package/bin/apifuse-pack-types.ts +26 -0
- package/bin/apifuse-perf.ts +4 -6
- package/bin/apifuse-record.ts +4 -7
- package/bin/apifuse-submit-check.ts +2 -2
- package/dist/define.d.ts +3 -2
- package/dist/define.js +1 -1
- package/dist/dev.js +3 -1
- package/dist/engine.d.ts +7 -6
- package/dist/engine.js +3 -4
- package/dist/error-resolution.js +21 -4
- package/dist/errors.d.ts +3 -0
- package/dist/errors.js +9 -0
- package/dist/handle-meta.d.ts +55 -0
- package/dist/handle-meta.js +57 -0
- package/dist/handle.d.ts +208 -0
- package/dist/handle.js +299 -0
- package/dist/i18n/catalog.js +21 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +6 -3
- package/dist/lint.d.ts +3 -0
- package/dist/lint.js +372 -47
- package/dist/provider.d.ts +5 -3
- package/dist/provider.js +4 -2
- package/dist/runtime/handle-wordlist.d.ts +43 -0
- package/dist/runtime/{choice-wordlist.js → handle-wordlist.js} +127 -9
- package/dist/runtime/handle.d.ts +71 -0
- package/dist/runtime/handle.js +900 -0
- package/dist/runtime/http-telemetry-binding.d.ts +10 -0
- package/dist/runtime/http-telemetry-binding.js +152 -0
- package/dist/runtime/http-telemetry-guard.d.ts +6 -0
- package/dist/runtime/http-telemetry-guard.js +150 -0
- package/dist/runtime/http-telemetry.d.ts +104 -0
- package/dist/runtime/http-telemetry.js +190 -0
- package/dist/runtime/http.d.ts +31 -0
- package/dist/runtime/http.js +222 -146
- package/dist/runtime/native-network.d.ts +10 -1
- package/dist/runtime/native-network.js +230 -29
- package/dist/runtime/native-telemetry.d.ts +142 -0
- package/dist/runtime/native-telemetry.js +231 -0
- package/dist/runtime/state.d.ts +4 -1
- package/dist/runtime/state.js +32 -17
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +2 -0
- package/dist/server/sensitive-values.d.ts +1 -1
- package/dist/server/sensitive-values.js +0 -3
- package/dist/server/serve-implementation.d.ts +6 -11
- package/dist/server/serve-implementation.js +119 -52
- package/dist/testing/run.js +10 -7
- package/dist/types.d.ts +4 -107
- package/package.json +1 -1
- package/src/define.ts +4 -3
- package/src/dev.ts +3 -1
- package/src/engine.ts +5 -6
- package/src/error-resolution.ts +21 -4
- package/src/errors.ts +13 -0
- package/src/handle-meta.ts +93 -0
- package/src/handle.ts +612 -0
- package/src/i18n/catalog.ts +23 -1
- package/src/index.ts +45 -13
- package/src/lint.ts +437 -55
- package/src/provider.ts +19 -20
- package/src/runtime/{choice-wordlist.ts → handle-wordlist.ts} +130 -9
- package/src/runtime/handle.ts +1172 -0
- package/src/runtime/http-telemetry-binding.ts +162 -0
- package/src/runtime/http-telemetry-guard.ts +155 -0
- package/src/runtime/http-telemetry.ts +292 -0
- package/src/runtime/http.ts +295 -173
- package/src/runtime/native-network.ts +281 -33
- package/src/runtime/native-telemetry.ts +366 -0
- package/src/runtime/state.ts +44 -16
- package/src/server/index.ts +30 -0
- package/src/server/sensitive-values.ts +0 -3
- package/src/server/serve-implementation.ts +127 -89
- package/src/testing/run.ts +11 -7
- package/src/types.ts +4 -137
- package/dist/choice-token.d.ts +0 -24
- package/dist/choice-token.js +0 -74
- package/dist/runtime/choice-wordlist.d.ts +0 -9
- package/dist/runtime/choice.d.ts +0 -27
- package/dist/runtime/choice.js +0 -801
- package/src/choice-token.ts +0 -165
- package/src/runtime/choice.ts +0 -1197
|
@@ -53,10 +53,8 @@ import {
|
|
|
53
53
|
import { createScratchpad } from "../runtime/auth-flow.js";
|
|
54
54
|
import type * as BrowserRuntimeModule from "../runtime/browser.js";
|
|
55
55
|
import { createProviderCache } from "../runtime/cache.js";
|
|
56
|
-
import {
|
|
57
|
-
|
|
58
|
-
PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
|
|
59
|
-
} from "../runtime/choice.js";
|
|
56
|
+
import type { HandleTelemetryEvent } from "../handle.js";
|
|
57
|
+
import { createHandleContext } from "../runtime/handle.js";
|
|
60
58
|
import { createCredentialContext } from "../runtime/credential.js";
|
|
61
59
|
import {
|
|
62
60
|
bindDiagnosticSensitiveRegistry,
|
|
@@ -78,6 +76,13 @@ import {
|
|
|
78
76
|
import { createEnvContext } from "../runtime/env.js";
|
|
79
77
|
import { executeOperation } from "../runtime/executor.js";
|
|
80
78
|
import { createHttpClient } from "../runtime/http.js";
|
|
79
|
+
import { NativeTelemetryCollector } from "../runtime/native-telemetry.js";
|
|
80
|
+
import {
|
|
81
|
+
bindHttpTelemetry,
|
|
82
|
+
isSupportedHttpBinding,
|
|
83
|
+
warnUnsupportedHttpBinding,
|
|
84
|
+
} from "../runtime/http-telemetry-binding.js";
|
|
85
|
+
import { HttpTelemetryCollector, type HttpTelemetryLogPayload } from "../runtime/http-telemetry.js";
|
|
81
86
|
import { wrapWithInstrumentation } from "../runtime/instrumentation.js";
|
|
82
87
|
import type * as NativeNetworkRuntimeModule from "../runtime/native-network.js";
|
|
83
88
|
import { createOcrClientFromEnv } from "../runtime/ocr.js";
|
|
@@ -154,7 +159,6 @@ import type {
|
|
|
154
159
|
BrowserClient,
|
|
155
160
|
FlowContext,
|
|
156
161
|
FlowContextStore,
|
|
157
|
-
HttpRetrySummary,
|
|
158
162
|
OcrContext,
|
|
159
163
|
OperationDefinition,
|
|
160
164
|
OperationErrorCode,
|
|
@@ -235,29 +239,7 @@ function providerErrorCode(error: unknown): string | undefined {
|
|
|
235
239
|
}
|
|
236
240
|
|
|
237
241
|
const AUTH_FLOW_LOCALES = ["en", "ko", "ja"] as const;
|
|
238
|
-
const retryResponseMeta = new WeakMap<ProviderContext,
|
|
239
|
-
type RetryLastErrorCode =
|
|
240
|
-
// runtime/http.ts:178-179 preserves ambient cancellation.
|
|
241
|
-
| "transport_cancelled"
|
|
242
|
-
// runtime/http.ts:180-196 normalizes timeout aborts and timeout-shaped errors.
|
|
243
|
-
| "transport_timeout"
|
|
244
|
-
// runtime/http.ts:198-225 normalizes other native fetch failures.
|
|
245
|
-
| "transport_network_error"
|
|
246
|
-
// runtime/http.ts:119-126,763-771,829-837 creates upstream status errors.
|
|
247
|
-
| "upstream_http_error"
|
|
248
|
-
// runtime/proxy-retry-policy.ts:64-84 forwards other coded TransportErrors; the builder closes them.
|
|
249
|
-
| "other";
|
|
250
|
-
const RETRY_LAST_ERROR_CODES = [
|
|
251
|
-
"transport_cancelled",
|
|
252
|
-
"transport_timeout",
|
|
253
|
-
"transport_network_error",
|
|
254
|
-
"upstream_http_error",
|
|
255
|
-
"other",
|
|
256
|
-
] as const satisfies readonly RetryLastErrorCode[];
|
|
257
|
-
|
|
258
|
-
function tenantRetryLastErrorCode(value: string): RetryLastErrorCode {
|
|
259
|
-
return RETRY_LAST_ERROR_CODES.find((candidate) => candidate === value) ?? "other";
|
|
260
|
-
}
|
|
242
|
+
const retryResponseMeta = new WeakMap<ProviderContext, HttpTelemetryCollector>();
|
|
261
243
|
const STATEFUL_INTERNAL_OPERATIONS_ROUTE = "/__apifuse/stateful/operations";
|
|
262
244
|
const STATEFUL_FORWARDING_SOURCE_POD_HEADER = "x-apifuse-stateful-source-pod";
|
|
263
245
|
const DEFAULT_STATEFUL_FORWARDING_MAX_SKEW_MS = 5 * 60_000;
|
|
@@ -845,6 +827,8 @@ type RequestScopeContext = {
|
|
|
845
827
|
trace: RuntimeTraceContext;
|
|
846
828
|
telemetry: RequestTelemetry;
|
|
847
829
|
resolverTelemetry: ResolverTelemetryCollector;
|
|
830
|
+
nativeTelemetry: NativeTelemetryCollector;
|
|
831
|
+
httpTelemetry: HttpTelemetryCollector;
|
|
848
832
|
};
|
|
849
833
|
|
|
850
834
|
function createProviderContext(
|
|
@@ -873,7 +857,6 @@ function createProviderContext(
|
|
|
873
857
|
request.requestId,
|
|
874
858
|
);
|
|
875
859
|
const cache = createProviderCache({ providerId: provider.id });
|
|
876
|
-
let wrappedContext: ProviderContext | undefined;
|
|
877
860
|
const { capabilityModules } = options;
|
|
878
861
|
const resolverOptions = createResolverRuntimeOptions(
|
|
879
862
|
provider,
|
|
@@ -921,10 +904,7 @@ function createProviderContext(
|
|
|
921
904
|
scope.redact,
|
|
922
905
|
);
|
|
923
906
|
|
|
924
|
-
const env = createEnvContext([
|
|
925
|
-
...providerSecretNames(provider),
|
|
926
|
-
PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
|
|
927
|
-
]);
|
|
907
|
+
const env = createEnvContext([...providerSecretNames(provider)]);
|
|
928
908
|
const credential = createCredentialContext({
|
|
929
909
|
allowedKeys: provider.credential?.keys,
|
|
930
910
|
mode: request.connection?.mode,
|
|
@@ -944,10 +924,7 @@ function createProviderContext(
|
|
|
944
924
|
http: createHttpClient(baseUrl, {
|
|
945
925
|
...proxyClientOptions,
|
|
946
926
|
...(signal ? { signal } : {}),
|
|
947
|
-
|
|
948
|
-
if (summary.attempts <= 1 || !wrappedContext) return;
|
|
949
|
-
retryResponseMeta.set(wrappedContext, summary);
|
|
950
|
-
},
|
|
927
|
+
httpTelemetry: scope.httpTelemetry,
|
|
951
928
|
}),
|
|
952
929
|
cache,
|
|
953
930
|
state: requestState,
|
|
@@ -971,6 +948,8 @@ function createProviderContext(
|
|
|
971
948
|
? {
|
|
972
949
|
native: {
|
|
973
950
|
network: capabilityModules.nativeNetwork!.createNativeNetworkClient({
|
|
951
|
+
telemetry: scope.telemetry.proxy,
|
|
952
|
+
nativeTelemetry: scope.nativeTelemetry,
|
|
974
953
|
egress: provider.native.network,
|
|
975
954
|
proxyPolicy: resolveNativeProxyPolicy(provider),
|
|
976
955
|
affinityKey: proxyClientOptions.affinityKey,
|
|
@@ -1004,24 +983,42 @@ function createProviderContext(
|
|
|
1004
983
|
createUnsupportedResolverClient("Provider does not declare resolver capability"),
|
|
1005
984
|
signal,
|
|
1006
985
|
),
|
|
1007
|
-
|
|
986
|
+
handle: createHandleContext({
|
|
1008
987
|
providerId: provider.id,
|
|
1009
|
-
env,
|
|
1010
988
|
request: requestContext,
|
|
1011
|
-
credential,
|
|
1012
989
|
state: requestState,
|
|
1013
990
|
onTelemetry: (event) =>
|
|
1014
991
|
(options.logger ?? defaultProviderServerLogger)({
|
|
1015
992
|
level: "info",
|
|
1016
|
-
event: "
|
|
993
|
+
event: "provider_handle",
|
|
1017
994
|
...event,
|
|
1018
995
|
}),
|
|
1019
996
|
}),
|
|
1020
997
|
};
|
|
1021
|
-
const
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
998
|
+
const candidateHttp = bindings.http;
|
|
999
|
+
const attached = options.engine.attach({ provider, bindings }) as ProviderContext;
|
|
1000
|
+
const hostHttp = provider.http ? attached.http : undefined;
|
|
1001
|
+
const supportedHostHttp = provider.http && isSupportedHttpBinding(hostHttp);
|
|
1002
|
+
if (provider.http && !supportedHostHttp) warnUnsupportedHttpBinding();
|
|
1003
|
+
const observedHttp =
|
|
1004
|
+
supportedHostHttp && hostHttp !== candidateHttp
|
|
1005
|
+
? bindHttpTelemetry(hostHttp, scope.httpTelemetry)
|
|
1006
|
+
: undefined;
|
|
1007
|
+
const contextTarget = observedHttp
|
|
1008
|
+
? new Proxy(attached, {
|
|
1009
|
+
get(target, property, receiver) {
|
|
1010
|
+
return property === "http" ? observedHttp : Reflect.get(target, property, receiver);
|
|
1011
|
+
},
|
|
1012
|
+
})
|
|
1013
|
+
: provider.http && !supportedHostHttp
|
|
1014
|
+
? new Proxy(attached, {
|
|
1015
|
+
get(target, property, receiver) {
|
|
1016
|
+
return property === "http" ? undefined : Reflect.get(target, property, receiver);
|
|
1017
|
+
},
|
|
1018
|
+
})
|
|
1019
|
+
: attached;
|
|
1020
|
+
const context = wrapWithInstrumentation(contextTarget);
|
|
1021
|
+
retryResponseMeta.set(context, scope.httpTelemetry);
|
|
1025
1022
|
return context;
|
|
1026
1023
|
}
|
|
1027
1024
|
|
|
@@ -1202,6 +1199,7 @@ function createAuthFlowContext(
|
|
|
1202
1199
|
http: createHttpClient(baseUrl, {
|
|
1203
1200
|
...proxyClientOptions,
|
|
1204
1201
|
...(signal ? { signal } : {}),
|
|
1202
|
+
httpTelemetry: scope.httpTelemetry,
|
|
1205
1203
|
}),
|
|
1206
1204
|
state: state.forConnection(resolveOperationConnectionId(request)),
|
|
1207
1205
|
stealth: stealthBaseUrl
|
|
@@ -1213,6 +1211,8 @@ function createAuthFlowContext(
|
|
|
1213
1211
|
? {
|
|
1214
1212
|
native: {
|
|
1215
1213
|
network: capabilityModules.nativeNetwork!.createNativeNetworkClient({
|
|
1214
|
+
telemetry: scope.telemetry.proxy,
|
|
1215
|
+
nativeTelemetry: scope.nativeTelemetry,
|
|
1216
1216
|
egress: provider.native.network,
|
|
1217
1217
|
proxyPolicy: resolveNativeProxyPolicy(provider),
|
|
1218
1218
|
affinityKey: proxyClientOptions.affinityKey,
|
|
@@ -1289,6 +1289,7 @@ type ProviderServerLogEventBase = ProviderRequestCost & {
|
|
|
1289
1289
|
status: number;
|
|
1290
1290
|
proxy?: ProxyTelemetryLogPayload;
|
|
1291
1291
|
resolver?: ResolverTelemetryLogPayload;
|
|
1292
|
+
http?: HttpTelemetryLogPayload;
|
|
1292
1293
|
};
|
|
1293
1294
|
|
|
1294
1295
|
export type ProviderServerLogEvent =
|
|
@@ -1318,18 +1319,10 @@ export type ProviderServerLogEvent =
|
|
|
1318
1319
|
providerId: string;
|
|
1319
1320
|
missingSecrets: string[];
|
|
1320
1321
|
}
|
|
1321
|
-
| {
|
|
1322
|
+
| ({
|
|
1322
1323
|
level: "info";
|
|
1323
|
-
event: "
|
|
1324
|
-
|
|
1325
|
-
purpose: string;
|
|
1326
|
-
operation: "parse" | "consume";
|
|
1327
|
-
format: "word" | "legacy";
|
|
1328
|
-
outcome: "success" | "not-found" | "invalid" | "unsupported" | "error";
|
|
1329
|
-
consumeMode: "never" | "on-parse" | "explicit";
|
|
1330
|
-
consumed: boolean;
|
|
1331
|
-
replay: boolean;
|
|
1332
|
-
}
|
|
1324
|
+
event: "provider_handle";
|
|
1325
|
+
} & HandleTelemetryEvent)
|
|
1333
1326
|
| {
|
|
1334
1327
|
level: "warn";
|
|
1335
1328
|
event: "provider_cleanup_failed";
|
|
@@ -1549,6 +1542,48 @@ function toErrorResponse(
|
|
|
1549
1542
|
// narrowing from a ProviderError-typed value would collapse the negative branch
|
|
1550
1543
|
// to `never`. Narrowing from unknown avoids that while still recognizing errors
|
|
1551
1544
|
// from a duplicate SDK module instance.
|
|
1545
|
+
function ownErrorCode(error: unknown): string | undefined {
|
|
1546
|
+
if (error === null || (typeof error !== "object" && typeof error !== "function")) {
|
|
1547
|
+
return undefined;
|
|
1548
|
+
}
|
|
1549
|
+
try {
|
|
1550
|
+
const descriptor = Object.getOwnPropertyDescriptor(error, "code");
|
|
1551
|
+
return descriptor && Object.hasOwn(descriptor, "value") && typeof descriptor.value === "string"
|
|
1552
|
+
? descriptor.value
|
|
1553
|
+
: undefined;
|
|
1554
|
+
} catch {
|
|
1555
|
+
return undefined;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
function errorChainHasCode(error: unknown, expected: string): boolean {
|
|
1560
|
+
const seen = new Set<object>();
|
|
1561
|
+
let current = error;
|
|
1562
|
+
for (let depth = 0; depth < 5; depth++) {
|
|
1563
|
+
if (current === null || (typeof current !== "object" && typeof current !== "function")) {
|
|
1564
|
+
return false;
|
|
1565
|
+
}
|
|
1566
|
+
if (seen.has(current)) return false;
|
|
1567
|
+
seen.add(current);
|
|
1568
|
+
let code: string | undefined;
|
|
1569
|
+
try {
|
|
1570
|
+
code = providerErrorCode(current) ?? ownErrorCode(current);
|
|
1571
|
+
} catch {
|
|
1572
|
+
code = ownErrorCode(current);
|
|
1573
|
+
}
|
|
1574
|
+
if (code === expected) return true;
|
|
1575
|
+
let cause: PropertyDescriptor | undefined;
|
|
1576
|
+
try {
|
|
1577
|
+
cause = Object.getOwnPropertyDescriptor(current, "cause");
|
|
1578
|
+
} catch {
|
|
1579
|
+
return false;
|
|
1580
|
+
}
|
|
1581
|
+
if (!cause || !Object.hasOwn(cause, "value")) return false;
|
|
1582
|
+
current = cause.value;
|
|
1583
|
+
}
|
|
1584
|
+
return false;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1552
1587
|
function providerObservabilityDetails(
|
|
1553
1588
|
error: unknown,
|
|
1554
1589
|
declaredErrorCode?: OperationErrorCode,
|
|
@@ -1577,26 +1612,45 @@ function providerObservabilityDetails(
|
|
|
1577
1612
|
retryable: providerErrorOption(error, "retryable") ?? declaredRetryable ?? false,
|
|
1578
1613
|
};
|
|
1579
1614
|
}
|
|
1615
|
+
// Native proxy resolution throws ProxyResolutionError directly, while HTTP
|
|
1616
|
+
// normalizes it beneath a transport_network_error cause and stealth retains
|
|
1617
|
+
// the original code. Preserve proxy attribution across all three forms.
|
|
1618
|
+
if (errorChainHasCode(error, "PROXY_REQUIRED")) {
|
|
1619
|
+
return {
|
|
1620
|
+
category: "proxy_pool",
|
|
1621
|
+
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
1622
|
+
retryable: true,
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1580
1625
|
if (!isTransportError(error)) {
|
|
1581
1626
|
return undefined;
|
|
1582
1627
|
}
|
|
1628
|
+
const errorCode = providerErrorCode(error);
|
|
1583
1629
|
const isProxyPoolCode =
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1630
|
+
errorCode === PROXY_POOL_EXHAUSTED_CODE ||
|
|
1631
|
+
errorCode === PROXY_EDGE_AUTH_REJECTED_CODE ||
|
|
1632
|
+
errorCode === "PROXY_ALLOCATION_FAILED" ||
|
|
1633
|
+
errorCode === "native_proxy_expired" ||
|
|
1634
|
+
errorCode === "native_proxy_invalid";
|
|
1587
1635
|
const category =
|
|
1588
1636
|
providerErrorOption(error, "category") ??
|
|
1589
1637
|
(isProxyPoolCode
|
|
1590
1638
|
? "proxy_pool"
|
|
1591
|
-
:
|
|
1639
|
+
: errorCode === PROXY_AUTH_IP_DENIED_CODE
|
|
1592
1640
|
? "anti_bot_blocked"
|
|
1593
|
-
:
|
|
1641
|
+
: errorCode === "transport_timeout" ||
|
|
1642
|
+
errorCode === "native_connection_timeout" ||
|
|
1643
|
+
errorCode === "native_connection_idle_timeout"
|
|
1594
1644
|
? "timeout"
|
|
1595
|
-
:
|
|
1645
|
+
: errorCode === "transport_network_error" ||
|
|
1646
|
+
errorCode === "native_connection_failed" ||
|
|
1647
|
+
errorCode === "native_connection_closed"
|
|
1596
1648
|
? "network"
|
|
1597
|
-
:
|
|
1598
|
-
?
|
|
1599
|
-
:
|
|
1649
|
+
: errorCode === "native_connection_aborted"
|
|
1650
|
+
? "client_cancelled"
|
|
1651
|
+
: error.upstreamStatus
|
|
1652
|
+
? categoryForStatus(error.upstreamStatus)
|
|
1653
|
+
: "upstream_http");
|
|
1600
1654
|
return {
|
|
1601
1655
|
category,
|
|
1602
1656
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
@@ -2223,9 +2277,13 @@ function createRequestScope(input: {
|
|
|
2223
2277
|
bindDiagnosticSensitiveRegistry(trace, sensitiveRegistry);
|
|
2224
2278
|
const proxyCollector = new ProxyTelemetryCollector();
|
|
2225
2279
|
const resolverCollector = new ResolverTelemetryCollector({ redact });
|
|
2280
|
+
const nativeCollector = new NativeTelemetryCollector({ redact });
|
|
2281
|
+
const httpCollector = new HttpTelemetryCollector({ redact });
|
|
2226
2282
|
const telemetry = new RequestTelemetry(trace);
|
|
2227
2283
|
telemetry.register(proxyCollector);
|
|
2228
2284
|
telemetry.register(resolverCollector);
|
|
2285
|
+
telemetry.register(nativeCollector);
|
|
2286
|
+
telemetry.register(httpCollector);
|
|
2229
2287
|
let rootRunner: <T>(fn: () => Promise<T>) => Promise<T> = (fn) => fn();
|
|
2230
2288
|
let resolveRoot!: (outcome: RequestTerminalOutcome) => void;
|
|
2231
2289
|
const rootTerminal = new Promise<RequestTerminalOutcome>((resolve) => {
|
|
@@ -2307,6 +2365,8 @@ function createRequestScope(input: {
|
|
|
2307
2365
|
trace,
|
|
2308
2366
|
telemetry,
|
|
2309
2367
|
resolverTelemetry: resolverCollector,
|
|
2368
|
+
nativeTelemetry: nativeCollector,
|
|
2369
|
+
httpTelemetry: httpCollector,
|
|
2310
2370
|
seedCredentials(rawBody, kind): void {
|
|
2311
2371
|
const harvested = rawRequestCredentials(rawBody, kind);
|
|
2312
2372
|
sensitiveRegistry.add(harvested.values);
|
|
@@ -2526,21 +2586,13 @@ function toJsonSuccessResponse(
|
|
|
2526
2586
|
}
|
|
2527
2587
|
|
|
2528
2588
|
const cacheMeta = ctx && "cache" in ctx ? ctx.cache.responseMeta() : undefined;
|
|
2529
|
-
const
|
|
2589
|
+
const retry = ctx ? retryResponseMeta.get(ctx)?.toTenantRetryPayload() : undefined;
|
|
2530
2590
|
type TenantCacheMeta = {
|
|
2531
2591
|
hit: boolean;
|
|
2532
2592
|
stale: boolean;
|
|
2533
2593
|
keys: TenantOpaqueKeys;
|
|
2534
2594
|
source?: ClosedEnum<NonNullable<ProviderCacheResponseMeta["source"]>>;
|
|
2535
2595
|
};
|
|
2536
|
-
type TenantRetryMeta = {
|
|
2537
|
-
attempts: number;
|
|
2538
|
-
retries: number;
|
|
2539
|
-
preset?: ClosedEnum<NonNullable<HttpRetrySummary["preset"]>>;
|
|
2540
|
-
transport: ClosedEnum<HttpRetrySummary["transport"]>;
|
|
2541
|
-
lastErrorCode?: ClosedEnum<RetryLastErrorCode>;
|
|
2542
|
-
lastStatus?: number;
|
|
2543
|
-
};
|
|
2544
2596
|
const cache: TenantNeutral<TenantCacheMeta> | undefined = cacheMeta
|
|
2545
2597
|
? {
|
|
2546
2598
|
hit: cacheMeta.hit,
|
|
@@ -2550,22 +2602,8 @@ function toJsonSuccessResponse(
|
|
|
2550
2602
|
...(cacheMeta.source ? { source: closedEnum(cacheMeta.source) } : {}),
|
|
2551
2603
|
}
|
|
2552
2604
|
: undefined;
|
|
2553
|
-
const retry: TenantNeutral<TenantRetryMeta> | undefined = retryMeta
|
|
2554
|
-
? {
|
|
2555
|
-
attempts: retryMeta.attempts,
|
|
2556
|
-
retries: retryMeta.retries,
|
|
2557
|
-
...(retryMeta.preset ? { preset: closedEnum(retryMeta.preset) } : {}),
|
|
2558
|
-
transport: closedEnum(retryMeta.transport),
|
|
2559
|
-
...(retryMeta.lastErrorCode
|
|
2560
|
-
? {
|
|
2561
|
-
lastErrorCode: closedEnum(tenantRetryLastErrorCode(retryMeta.lastErrorCode)),
|
|
2562
|
-
}
|
|
2563
|
-
: {}),
|
|
2564
|
-
...(retryMeta.lastStatus ? { lastStatus: retryMeta.lastStatus } : {}),
|
|
2565
|
-
}
|
|
2566
|
-
: undefined;
|
|
2567
2605
|
const meta =
|
|
2568
|
-
cacheMeta ||
|
|
2606
|
+
cacheMeta || retry
|
|
2569
2607
|
? {
|
|
2570
2608
|
...(cache
|
|
2571
2609
|
? {
|
package/src/testing/run.ts
CHANGED
|
@@ -2,7 +2,13 @@ import { describe, expect, it } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import { ProviderError } from "../errors.js";
|
|
4
4
|
import { createProviderCache } from "../runtime/cache.js";
|
|
5
|
-
import {
|
|
5
|
+
import { createTestHandleContext } from "../runtime/handle.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Stable connection scope for the local test harness so bound handles (drafts,
|
|
9
|
+
* default cursors) can be exercised exactly as in a connected production call.
|
|
10
|
+
*/
|
|
11
|
+
const STANDARD_TEST_CONNECTION_ID = "standard-test-connection";
|
|
6
12
|
import { createMemoryProviderRuntimeState } from "../runtime/state.js";
|
|
7
13
|
import { createUnsupportedOcrClient } from "../runtime/ocr.js";
|
|
8
14
|
import { createUnsupportedSttClient } from "../runtime/stt.js";
|
|
@@ -319,7 +325,7 @@ function createUpstreamContext(
|
|
|
319
325
|
getAccessToken: () => undefined,
|
|
320
326
|
getScopes: () => [],
|
|
321
327
|
};
|
|
322
|
-
const request = { headers: {} };
|
|
328
|
+
const request = { headers: {}, connectionId: STANDARD_TEST_CONNECTION_ID };
|
|
323
329
|
const state = createMemoryProviderRuntimeState();
|
|
324
330
|
const nativeEgress = provider.native
|
|
325
331
|
? createNativeEgressAuthorization({ egress: provider.native.network })
|
|
@@ -578,10 +584,9 @@ function createUpstreamContext(
|
|
|
578
584
|
resolver: {
|
|
579
585
|
solve: async () => unsupported("ctx.resolver.solve"),
|
|
580
586
|
},
|
|
581
|
-
|
|
587
|
+
handle: createTestHandleContext({
|
|
582
588
|
providerId: `standard-test-${operationName}`,
|
|
583
589
|
request,
|
|
584
|
-
credential,
|
|
585
590
|
state,
|
|
586
591
|
}),
|
|
587
592
|
} as ProviderContext;
|
|
@@ -622,7 +627,7 @@ export function createSnapshotContext(rawFixture: unknown): ProviderContext {
|
|
|
622
627
|
getAccessToken: () => undefined,
|
|
623
628
|
getScopes: () => [],
|
|
624
629
|
};
|
|
625
|
-
const request = { headers: {} };
|
|
630
|
+
const request = { headers: {}, connectionId: STANDARD_TEST_CONNECTION_ID };
|
|
626
631
|
const state = createMemoryProviderRuntimeState();
|
|
627
632
|
const streamCaptureGroup = findStreamCaptureGroup(rawFixture);
|
|
628
633
|
let nextCaptureItem = 0;
|
|
@@ -709,10 +714,9 @@ export function createSnapshotContext(rawFixture: unknown): ProviderContext {
|
|
|
709
714
|
resolver: {
|
|
710
715
|
solve: async () => unsupported("ctx.resolver.solve"),
|
|
711
716
|
},
|
|
712
|
-
|
|
717
|
+
handle: createTestHandleContext({
|
|
713
718
|
providerId: "standard-test",
|
|
714
719
|
request,
|
|
715
|
-
credential,
|
|
716
720
|
state,
|
|
717
721
|
}),
|
|
718
722
|
} satisfies Omit<ProviderContext, "native"> as unknown as ProviderContext;
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type ms from "ms";
|
|
2
|
+
import type { HandleContext, HandleKind } from "./handle.js";
|
|
2
3
|
import type { HealthScenario } from "./health-scenario.js";
|
|
3
4
|
import type { SerializedCookieJar } from "tough-cookie";
|
|
4
5
|
|
|
@@ -1997,140 +1998,6 @@ export interface ProviderRequestContext {
|
|
|
1997
1998
|
headers: Record<string, string>;
|
|
1998
1999
|
}
|
|
1999
2000
|
|
|
2000
|
-
export interface ProviderChoiceBindingOptions {
|
|
2001
|
-
connection?: boolean;
|
|
2002
|
-
credentialKeys?: readonly string[];
|
|
2003
|
-
}
|
|
2004
|
-
|
|
2005
|
-
export type ProviderChoiceConsumeMode = "never" | "on-parse" | "explicit";
|
|
2006
|
-
|
|
2007
|
-
export type ProviderChoiceConsumeResult =
|
|
2008
|
-
| { readonly status: "consumed" }
|
|
2009
|
-
| { readonly status: "already-consumed" }
|
|
2010
|
-
| { readonly status: "unsupported" };
|
|
2011
|
-
|
|
2012
|
-
export type ProviderChoiceExplicitParseResult =
|
|
2013
|
-
| {
|
|
2014
|
-
readonly status: "active";
|
|
2015
|
-
readonly payload: Record<string, unknown>;
|
|
2016
|
-
/** Stable, opaque key for provider-owned idempotency records. */
|
|
2017
|
-
readonly replayKey: string;
|
|
2018
|
-
/** Atomically claims a word token. Inline tokens report unsupported. */
|
|
2019
|
-
consume(): Promise<ProviderChoiceConsumeResult>;
|
|
2020
|
-
}
|
|
2021
|
-
| {
|
|
2022
|
-
readonly status: "consumed";
|
|
2023
|
-
/** Use this key to read the provider-owned result before returning an error. */
|
|
2024
|
-
readonly replayKey: string;
|
|
2025
|
-
};
|
|
2026
|
-
|
|
2027
|
-
export type ProviderChoiceStorageOptions =
|
|
2028
|
-
| {
|
|
2029
|
-
readonly mode: "inline";
|
|
2030
|
-
}
|
|
2031
|
-
| {
|
|
2032
|
-
readonly mode: "server";
|
|
2033
|
-
readonly namespace: string;
|
|
2034
|
-
readonly state?: ProviderRuntimeState;
|
|
2035
|
-
readonly ttl?: ProviderStateDurationString;
|
|
2036
|
-
readonly maxEntries: number;
|
|
2037
|
-
readonly maxValueBytes: number;
|
|
2038
|
-
readonly unavailable?: "reject";
|
|
2039
|
-
}
|
|
2040
|
-
| {
|
|
2041
|
-
readonly mode: "auto";
|
|
2042
|
-
readonly namespace: string;
|
|
2043
|
-
readonly state?: ProviderRuntimeState;
|
|
2044
|
-
readonly ttl?: ProviderStateDurationString;
|
|
2045
|
-
readonly maxInlineBytes: number;
|
|
2046
|
-
readonly maxEntries: number;
|
|
2047
|
-
readonly maxValueBytes: number;
|
|
2048
|
-
readonly unavailable?: "reject";
|
|
2049
|
-
};
|
|
2050
|
-
|
|
2051
|
-
export interface ProviderChoiceIssueOptions<
|
|
2052
|
-
TPayload extends Record<string, unknown>,
|
|
2053
|
-
> {
|
|
2054
|
-
prefix: string;
|
|
2055
|
-
purpose: string;
|
|
2056
|
-
payload: TPayload;
|
|
2057
|
-
ttlMs: number;
|
|
2058
|
-
nowMs?: number;
|
|
2059
|
-
bind?: ProviderChoiceBindingOptions;
|
|
2060
|
-
/** Server storage only: standard emits four words; high emits five. */
|
|
2061
|
-
strength?: "standard" | "high";
|
|
2062
|
-
storage?: ProviderChoiceStorageOptions;
|
|
2063
|
-
}
|
|
2064
|
-
|
|
2065
|
-
export interface ProviderChoiceParseOptions {
|
|
2066
|
-
token: string;
|
|
2067
|
-
prefix: string;
|
|
2068
|
-
purpose: string;
|
|
2069
|
-
ttlMs?: number;
|
|
2070
|
-
nowMs?: number;
|
|
2071
|
-
futureToleranceMs?: number;
|
|
2072
|
-
bind?: ProviderChoiceBindingOptions;
|
|
2073
|
-
storage?: ProviderChoiceStorageOptions;
|
|
2074
|
-
/** Defaults to never, preserving reusable choice-token parse semantics. */
|
|
2075
|
-
consume?: ProviderChoiceConsumeMode;
|
|
2076
|
-
}
|
|
2077
|
-
|
|
2078
|
-
export interface ProviderChoiceContext {
|
|
2079
|
-
issue<TPayload extends Record<string, unknown>>(
|
|
2080
|
-
options: ProviderChoiceIssueOptions<TPayload> & {
|
|
2081
|
-
readonly storage?: { readonly mode: "inline" };
|
|
2082
|
-
},
|
|
2083
|
-
): string;
|
|
2084
|
-
issue<TPayload extends Record<string, unknown>>(
|
|
2085
|
-
options: ProviderChoiceIssueOptions<TPayload> & {
|
|
2086
|
-
readonly storage: Extract<
|
|
2087
|
-
ProviderChoiceStorageOptions,
|
|
2088
|
-
{ readonly mode: "server" }
|
|
2089
|
-
>;
|
|
2090
|
-
},
|
|
2091
|
-
): Promise<string>;
|
|
2092
|
-
issue<TPayload extends Record<string, unknown>>(
|
|
2093
|
-
options: ProviderChoiceIssueOptions<TPayload> & {
|
|
2094
|
-
readonly storage: Extract<
|
|
2095
|
-
ProviderChoiceStorageOptions,
|
|
2096
|
-
{ readonly mode: "auto" }
|
|
2097
|
-
>;
|
|
2098
|
-
},
|
|
2099
|
-
): string | Promise<string>;
|
|
2100
|
-
issue<TPayload extends Record<string, unknown>>(
|
|
2101
|
-
options: ProviderChoiceIssueOptions<TPayload>,
|
|
2102
|
-
): string | Promise<string>;
|
|
2103
|
-
parse(
|
|
2104
|
-
options: ProviderChoiceParseOptions & { readonly consume: "explicit" },
|
|
2105
|
-
): Promise<ProviderChoiceExplicitParseResult>;
|
|
2106
|
-
parse(
|
|
2107
|
-
options: ProviderChoiceParseOptions & {
|
|
2108
|
-
readonly storage?: { readonly mode: "inline" };
|
|
2109
|
-
},
|
|
2110
|
-
): Record<string, unknown>;
|
|
2111
|
-
parse(
|
|
2112
|
-
options: ProviderChoiceParseOptions & {
|
|
2113
|
-
readonly storage: Extract<
|
|
2114
|
-
ProviderChoiceStorageOptions,
|
|
2115
|
-
{ readonly mode: "server" }
|
|
2116
|
-
>;
|
|
2117
|
-
},
|
|
2118
|
-
): Promise<Record<string, unknown>>;
|
|
2119
|
-
parse(
|
|
2120
|
-
options: ProviderChoiceParseOptions & {
|
|
2121
|
-
readonly storage: Extract<
|
|
2122
|
-
ProviderChoiceStorageOptions,
|
|
2123
|
-
{ readonly mode: "auto" }
|
|
2124
|
-
>;
|
|
2125
|
-
},
|
|
2126
|
-
): Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
2127
|
-
parse(
|
|
2128
|
-
options: ProviderChoiceParseOptions,
|
|
2129
|
-
):
|
|
2130
|
-
| Record<string, unknown>
|
|
2131
|
-
| Promise<Record<string, unknown> | ProviderChoiceExplicitParseResult>;
|
|
2132
|
-
}
|
|
2133
|
-
|
|
2134
2001
|
export interface ContextScratchpad {
|
|
2135
2002
|
get(key: string): unknown;
|
|
2136
2003
|
set(key: string, value: unknown): void;
|
|
@@ -2402,8 +2269,8 @@ export type ProviderContext<TConfig = Record<string, unknown>> = {
|
|
|
2402
2269
|
& ("ocr" extends keyof TConfig ? { ocr: OcrContext } : Record<never, never>)
|
|
2403
2270
|
& ("stt" extends keyof TConfig ? { stt: SttContext } : Record<never, never>)
|
|
2404
2271
|
& ("resolver" extends keyof TConfig ? { resolver: ResolverContext } : Record<never, never>)
|
|
2405
|
-
& ("
|
|
2406
|
-
? {
|
|
2272
|
+
& ("handle" extends keyof TConfig
|
|
2273
|
+
? { handle: HandleContext }
|
|
2407
2274
|
: Record<never, never>);
|
|
2408
2275
|
|
|
2409
2276
|
/** Backwards-compatible name for a declaration-derived provider context. */
|
|
@@ -2588,7 +2455,7 @@ export interface ProviderDefinition<TContext = ProviderContext> {
|
|
|
2588
2455
|
engine: BrowserEngine;
|
|
2589
2456
|
};
|
|
2590
2457
|
auth?: AuthConfig;
|
|
2591
|
-
|
|
2458
|
+
handle?: readonly HandleKind[];
|
|
2592
2459
|
reviewed?: ProviderReviewed;
|
|
2593
2460
|
access?: ProviderAccessConfig;
|
|
2594
2461
|
secrets?: ProviderSecretDeclaration[];
|
package/dist/choice-token.d.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export type ProviderChoiceTokenPayload = Record<string, unknown>;
|
|
2
|
-
export type ProviderChoiceTokenErrorReason = "invalid_shape" | "invalid_signature" | "invalid_payload" | "invalid_binding" | "stale";
|
|
3
|
-
export declare class ProviderChoiceTokenError extends Error {
|
|
4
|
-
readonly reason: ProviderChoiceTokenErrorReason;
|
|
5
|
-
constructor(reason: ProviderChoiceTokenErrorReason, message: string);
|
|
6
|
-
}
|
|
7
|
-
export interface CreateProviderChoiceTokenOptions<TPayload extends ProviderChoiceTokenPayload> {
|
|
8
|
-
prefix: string;
|
|
9
|
-
payload: TPayload;
|
|
10
|
-
secret: string;
|
|
11
|
-
}
|
|
12
|
-
export interface ParseProviderChoiceTokenOptions {
|
|
13
|
-
token: string;
|
|
14
|
-
prefix: string;
|
|
15
|
-
secret: string;
|
|
16
|
-
}
|
|
17
|
-
export interface FreshProviderChoiceIssuedAtOptions {
|
|
18
|
-
ttlMs: number;
|
|
19
|
-
nowMs?: number;
|
|
20
|
-
futureToleranceMs?: number;
|
|
21
|
-
}
|
|
22
|
-
export declare function createProviderChoiceToken<TPayload extends ProviderChoiceTokenPayload>(options: CreateProviderChoiceTokenOptions<TPayload>): string;
|
|
23
|
-
export declare function parseProviderChoiceToken(options: ParseProviderChoiceTokenOptions): ProviderChoiceTokenPayload;
|
|
24
|
-
export declare function assertFreshProviderChoiceIssuedAt(issuedAtMs: unknown, options: FreshProviderChoiceIssuedAtOptions): number;
|