@apifuse/provider-sdk 2.2.0-beta.60 → 2.2.0-beta.61

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.
Files changed (73) hide show
  1. package/AUTHORING.md +17 -16
  2. package/CHANGELOG.md +4 -0
  3. package/bin/apifuse-dev.ts +5 -7
  4. package/bin/apifuse-pack-types.ts +22 -0
  5. package/bin/apifuse-perf.ts +4 -6
  6. package/bin/apifuse-record.ts +4 -7
  7. package/bin/apifuse-submit-check.ts +2 -2
  8. package/dist/define.d.ts +3 -2
  9. package/dist/define.js +1 -1
  10. package/dist/dev.js +3 -1
  11. package/dist/engine.d.ts +7 -6
  12. package/dist/engine.js +3 -4
  13. package/dist/error-resolution.js +21 -4
  14. package/dist/errors.d.ts +3 -0
  15. package/dist/errors.js +9 -0
  16. package/dist/handle-meta.d.ts +39 -0
  17. package/dist/handle-meta.js +29 -0
  18. package/dist/handle.d.ts +187 -0
  19. package/dist/handle.js +254 -0
  20. package/dist/i18n/catalog.js +21 -1
  21. package/dist/index.d.ts +4 -3
  22. package/dist/index.js +3 -2
  23. package/dist/lint.d.ts +3 -0
  24. package/dist/lint.js +352 -47
  25. package/dist/provider.d.ts +3 -3
  26. package/dist/provider.js +2 -2
  27. package/dist/runtime/handle-wordlist.d.ts +43 -0
  28. package/dist/runtime/{choice-wordlist.js → handle-wordlist.js} +127 -9
  29. package/dist/runtime/handle.d.ts +71 -0
  30. package/dist/runtime/handle.js +895 -0
  31. package/dist/runtime/native-network.d.ts +10 -1
  32. package/dist/runtime/native-network.js +230 -29
  33. package/dist/runtime/native-telemetry.d.ts +142 -0
  34. package/dist/runtime/native-telemetry.js +231 -0
  35. package/dist/runtime/state.d.ts +4 -1
  36. package/dist/runtime/state.js +32 -17
  37. package/dist/server/index.d.ts +1 -0
  38. package/dist/server/index.js +1 -0
  39. package/dist/server/sensitive-values.d.ts +1 -1
  40. package/dist/server/sensitive-values.js +0 -3
  41. package/dist/server/serve-implementation.d.ts +4 -11
  42. package/dist/server/serve-implementation.js +86 -18
  43. package/dist/testing/run.js +10 -7
  44. package/dist/types.d.ts +4 -107
  45. package/package.json +1 -1
  46. package/src/define.ts +4 -3
  47. package/src/dev.ts +3 -1
  48. package/src/engine.ts +5 -6
  49. package/src/error-resolution.ts +21 -4
  50. package/src/errors.ts +13 -0
  51. package/src/handle-meta.ts +58 -0
  52. package/src/handle.ts +529 -0
  53. package/src/i18n/catalog.ts +23 -1
  54. package/src/index.ts +26 -13
  55. package/src/lint.ts +414 -55
  56. package/src/provider.ts +9 -20
  57. package/src/runtime/{choice-wordlist.ts → handle-wordlist.ts} +130 -9
  58. package/src/runtime/handle.ts +1162 -0
  59. package/src/runtime/native-network.ts +281 -33
  60. package/src/runtime/native-telemetry.ts +366 -0
  61. package/src/runtime/state.ts +44 -16
  62. package/src/server/index.ts +17 -0
  63. package/src/server/sensitive-values.ts +0 -3
  64. package/src/server/serve-implementation.ts +87 -32
  65. package/src/testing/run.ts +11 -7
  66. package/src/types.ts +4 -137
  67. package/dist/choice-token.d.ts +0 -24
  68. package/dist/choice-token.js +0 -74
  69. package/dist/runtime/choice-wordlist.d.ts +0 -9
  70. package/dist/runtime/choice.d.ts +0 -27
  71. package/dist/runtime/choice.js +0 -801
  72. package/src/choice-token.ts +0 -165
  73. package/src/runtime/choice.ts +0 -1197
@@ -1,3 +1,4 @@
1
+ import { NativeTelemetryCollector } from "../runtime/native-telemetry.js";
1
2
  import { STATUS_CODES } from "node:http";
2
3
  import { readDiagnosticEnv, withDiagnosticEnv } from "../runtime/diagnostic-env.js";
3
4
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -53,10 +54,8 @@ import {
53
54
  import { createScratchpad } from "../runtime/auth-flow.js";
54
55
  import type * as BrowserRuntimeModule from "../runtime/browser.js";
55
56
  import { createProviderCache } from "../runtime/cache.js";
56
- import {
57
- createProviderChoiceContext,
58
- PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
59
- } from "../runtime/choice.js";
57
+ import type { HandleTelemetryEvent } from "../handle.js";
58
+ import { createHandleContext } from "../runtime/handle.js";
60
59
  import { createCredentialContext } from "../runtime/credential.js";
61
60
  import {
62
61
  bindDiagnosticSensitiveRegistry,
@@ -845,6 +844,7 @@ type RequestScopeContext = {
845
844
  trace: RuntimeTraceContext;
846
845
  telemetry: RequestTelemetry;
847
846
  resolverTelemetry: ResolverTelemetryCollector;
847
+ nativeTelemetry: NativeTelemetryCollector;
848
848
  };
849
849
 
850
850
  function createProviderContext(
@@ -921,10 +921,7 @@ function createProviderContext(
921
921
  scope.redact,
922
922
  );
923
923
 
924
- const env = createEnvContext([
925
- ...providerSecretNames(provider),
926
- PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
927
- ]);
924
+ const env = createEnvContext([...providerSecretNames(provider)]);
928
925
  const credential = createCredentialContext({
929
926
  allowedKeys: provider.credential?.keys,
930
927
  mode: request.connection?.mode,
@@ -971,6 +968,8 @@ function createProviderContext(
971
968
  ? {
972
969
  native: {
973
970
  network: capabilityModules.nativeNetwork!.createNativeNetworkClient({
971
+ telemetry: scope.telemetry.proxy,
972
+ nativeTelemetry: scope.nativeTelemetry,
974
973
  egress: provider.native.network,
975
974
  proxyPolicy: resolveNativeProxyPolicy(provider),
976
975
  affinityKey: proxyClientOptions.affinityKey,
@@ -1004,16 +1003,14 @@ function createProviderContext(
1004
1003
  createUnsupportedResolverClient("Provider does not declare resolver capability"),
1005
1004
  signal,
1006
1005
  ),
1007
- choice: createProviderChoiceContext({
1006
+ handle: createHandleContext({
1008
1007
  providerId: provider.id,
1009
- env,
1010
1008
  request: requestContext,
1011
- credential,
1012
1009
  state: requestState,
1013
1010
  onTelemetry: (event) =>
1014
1011
  (options.logger ?? defaultProviderServerLogger)({
1015
1012
  level: "info",
1016
- event: "provider_choice_token",
1013
+ event: "provider_handle",
1017
1014
  ...event,
1018
1015
  }),
1019
1016
  }),
@@ -1213,6 +1210,8 @@ function createAuthFlowContext(
1213
1210
  ? {
1214
1211
  native: {
1215
1212
  network: capabilityModules.nativeNetwork!.createNativeNetworkClient({
1213
+ telemetry: scope.telemetry.proxy,
1214
+ nativeTelemetry: scope.nativeTelemetry,
1216
1215
  egress: provider.native.network,
1217
1216
  proxyPolicy: resolveNativeProxyPolicy(provider),
1218
1217
  affinityKey: proxyClientOptions.affinityKey,
@@ -1318,18 +1317,10 @@ export type ProviderServerLogEvent =
1318
1317
  providerId: string;
1319
1318
  missingSecrets: string[];
1320
1319
  }
1321
- | {
1320
+ | ({
1322
1321
  level: "info";
1323
- event: "provider_choice_token";
1324
- providerId: string;
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
- }
1322
+ event: "provider_handle";
1323
+ } & HandleTelemetryEvent)
1333
1324
  | {
1334
1325
  level: "warn";
1335
1326
  event: "provider_cleanup_failed";
@@ -1549,6 +1540,48 @@ function toErrorResponse(
1549
1540
  // narrowing from a ProviderError-typed value would collapse the negative branch
1550
1541
  // to `never`. Narrowing from unknown avoids that while still recognizing errors
1551
1542
  // from a duplicate SDK module instance.
1543
+ function ownErrorCode(error: unknown): string | undefined {
1544
+ if (error === null || (typeof error !== "object" && typeof error !== "function")) {
1545
+ return undefined;
1546
+ }
1547
+ try {
1548
+ const descriptor = Object.getOwnPropertyDescriptor(error, "code");
1549
+ return descriptor && Object.hasOwn(descriptor, "value") && typeof descriptor.value === "string"
1550
+ ? descriptor.value
1551
+ : undefined;
1552
+ } catch {
1553
+ return undefined;
1554
+ }
1555
+ }
1556
+
1557
+ function errorChainHasCode(error: unknown, expected: string): boolean {
1558
+ const seen = new Set<object>();
1559
+ let current = error;
1560
+ for (let depth = 0; depth < 5; depth++) {
1561
+ if (current === null || (typeof current !== "object" && typeof current !== "function")) {
1562
+ return false;
1563
+ }
1564
+ if (seen.has(current)) return false;
1565
+ seen.add(current);
1566
+ let code: string | undefined;
1567
+ try {
1568
+ code = providerErrorCode(current) ?? ownErrorCode(current);
1569
+ } catch {
1570
+ code = ownErrorCode(current);
1571
+ }
1572
+ if (code === expected) return true;
1573
+ let cause: PropertyDescriptor | undefined;
1574
+ try {
1575
+ cause = Object.getOwnPropertyDescriptor(current, "cause");
1576
+ } catch {
1577
+ return false;
1578
+ }
1579
+ if (!cause || !Object.hasOwn(cause, "value")) return false;
1580
+ current = cause.value;
1581
+ }
1582
+ return false;
1583
+ }
1584
+
1552
1585
  function providerObservabilityDetails(
1553
1586
  error: unknown,
1554
1587
  declaredErrorCode?: OperationErrorCode,
@@ -1577,26 +1610,45 @@ function providerObservabilityDetails(
1577
1610
  retryable: providerErrorOption(error, "retryable") ?? declaredRetryable ?? false,
1578
1611
  };
1579
1612
  }
1613
+ // Native proxy resolution throws ProxyResolutionError directly, while HTTP
1614
+ // normalizes it beneath a transport_network_error cause and stealth retains
1615
+ // the original code. Preserve proxy attribution across all three forms.
1616
+ if (errorChainHasCode(error, "PROXY_REQUIRED")) {
1617
+ return {
1618
+ category: "proxy_pool",
1619
+ taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
1620
+ retryable: true,
1621
+ };
1622
+ }
1580
1623
  if (!isTransportError(error)) {
1581
1624
  return undefined;
1582
1625
  }
1626
+ const errorCode = providerErrorCode(error);
1583
1627
  const isProxyPoolCode =
1584
- providerErrorCode(error) === PROXY_POOL_EXHAUSTED_CODE ||
1585
- providerErrorCode(error) === PROXY_EDGE_AUTH_REJECTED_CODE ||
1586
- providerErrorCode(error) === "PROXY_ALLOCATION_FAILED";
1628
+ errorCode === PROXY_POOL_EXHAUSTED_CODE ||
1629
+ errorCode === PROXY_EDGE_AUTH_REJECTED_CODE ||
1630
+ errorCode === "PROXY_ALLOCATION_FAILED" ||
1631
+ errorCode === "native_proxy_expired" ||
1632
+ errorCode === "native_proxy_invalid";
1587
1633
  const category =
1588
1634
  providerErrorOption(error, "category") ??
1589
1635
  (isProxyPoolCode
1590
1636
  ? "proxy_pool"
1591
- : providerErrorCode(error) === PROXY_AUTH_IP_DENIED_CODE
1637
+ : errorCode === PROXY_AUTH_IP_DENIED_CODE
1592
1638
  ? "anti_bot_blocked"
1593
- : providerErrorCode(error) === "transport_timeout"
1639
+ : errorCode === "transport_timeout" ||
1640
+ errorCode === "native_connection_timeout" ||
1641
+ errorCode === "native_connection_idle_timeout"
1594
1642
  ? "timeout"
1595
- : providerErrorCode(error) === "transport_network_error"
1643
+ : errorCode === "transport_network_error" ||
1644
+ errorCode === "native_connection_failed" ||
1645
+ errorCode === "native_connection_closed"
1596
1646
  ? "network"
1597
- : error.upstreamStatus
1598
- ? categoryForStatus(error.upstreamStatus)
1599
- : "upstream_http");
1647
+ : errorCode === "native_connection_aborted"
1648
+ ? "client_cancelled"
1649
+ : error.upstreamStatus
1650
+ ? categoryForStatus(error.upstreamStatus)
1651
+ : "upstream_http");
1600
1652
  return {
1601
1653
  category,
1602
1654
  taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
@@ -2223,9 +2275,11 @@ function createRequestScope(input: {
2223
2275
  bindDiagnosticSensitiveRegistry(trace, sensitiveRegistry);
2224
2276
  const proxyCollector = new ProxyTelemetryCollector();
2225
2277
  const resolverCollector = new ResolverTelemetryCollector({ redact });
2278
+ const nativeCollector = new NativeTelemetryCollector({ redact });
2226
2279
  const telemetry = new RequestTelemetry(trace);
2227
2280
  telemetry.register(proxyCollector);
2228
2281
  telemetry.register(resolverCollector);
2282
+ telemetry.register(nativeCollector);
2229
2283
  let rootRunner: <T>(fn: () => Promise<T>) => Promise<T> = (fn) => fn();
2230
2284
  let resolveRoot!: (outcome: RequestTerminalOutcome) => void;
2231
2285
  const rootTerminal = new Promise<RequestTerminalOutcome>((resolve) => {
@@ -2307,6 +2361,7 @@ function createRequestScope(input: {
2307
2361
  trace,
2308
2362
  telemetry,
2309
2363
  resolverTelemetry: resolverCollector,
2364
+ nativeTelemetry: nativeCollector,
2310
2365
  seedCredentials(rawBody, kind): void {
2311
2366
  const harvested = rawRequestCredentials(rawBody, kind);
2312
2367
  sensitiveRegistry.add(harvested.values);
@@ -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 { createTestProviderChoiceContext } from "../runtime/choice.js";
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
- choice: createTestProviderChoiceContext({
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
- choice: createTestProviderChoiceContext({
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
- & ("choice" extends keyof TConfig
2406
- ? { choice: ProviderChoiceContext }
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
- choice?: Record<string, never> | true;
2458
+ handle?: readonly HandleKind[];
2592
2459
  reviewed?: ProviderReviewed;
2593
2460
  access?: ProviderAccessConfig;
2594
2461
  secrets?: ProviderSecretDeclaration[];
@@ -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;
@@ -1,74 +0,0 @@
1
- import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes, timingSafeEqual, } from "node:crypto";
2
- export class ProviderChoiceTokenError extends Error {
3
- reason;
4
- constructor(reason, message) {
5
- super(message);
6
- this.name = "ProviderChoiceTokenError";
7
- this.reason = reason;
8
- }
9
- }
10
- export function createProviderChoiceToken(options) {
11
- const iv = randomBytes(12);
12
- const cipher = createCipheriv("aes-256-gcm", choiceEncryptionKey(options.secret), iv);
13
- const encryptedPayload = Buffer.concat([
14
- cipher.update(JSON.stringify(options.payload), "utf8"),
15
- cipher.final(),
16
- ]).toString("base64url");
17
- const authTag = cipher.getAuthTag().toString("base64url");
18
- const encodedIv = iv.toString("base64url");
19
- const signature = signProviderChoiceTokenBody(`${options.prefix}.${encodedIv}.${encryptedPayload}.${authTag}`, options.secret);
20
- return `${options.prefix}.${encodedIv}.${encryptedPayload}.${authTag}.${signature}`;
21
- }
22
- export function parseProviderChoiceToken(options) {
23
- const [actualPrefix, encodedIv, encryptedPayload, authTag, signature, ...extra] = options.token.split(".");
24
- if (actualPrefix !== options.prefix ||
25
- !encodedIv ||
26
- !encryptedPayload ||
27
- !authTag ||
28
- !signature ||
29
- extra.length > 0) {
30
- throw new ProviderChoiceTokenError("invalid_shape", "Provider choice token shape is invalid.");
31
- }
32
- const signedBody = `${options.prefix}.${encodedIv}.${encryptedPayload}.${authTag}`;
33
- const expectedSignature = signProviderChoiceTokenBody(signedBody, options.secret);
34
- const actualBuffer = Buffer.from(signature);
35
- const expectedBuffer = Buffer.from(expectedSignature);
36
- if (actualBuffer.length !== expectedBuffer.length ||
37
- !timingSafeEqual(actualBuffer, expectedBuffer)) {
38
- throw new ProviderChoiceTokenError("invalid_signature", "Provider choice token signature is invalid.");
39
- }
40
- try {
41
- const decipher = createDecipheriv("aes-256-gcm", choiceEncryptionKey(options.secret), Buffer.from(encodedIv, "base64url"));
42
- decipher.setAuthTag(Buffer.from(authTag, "base64url"));
43
- const decrypted = Buffer.concat([
44
- decipher.update(Buffer.from(encryptedPayload, "base64url")),
45
- decipher.final(),
46
- ]).toString("utf8");
47
- const parsed = JSON.parse(decrypted);
48
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
49
- throw new Error("payload is not an object");
50
- }
51
- return Object.fromEntries(Object.entries(parsed));
52
- }
53
- catch {
54
- throw new ProviderChoiceTokenError("invalid_payload", "Provider choice token payload is invalid.");
55
- }
56
- }
57
- export function assertFreshProviderChoiceIssuedAt(issuedAtMs, options) {
58
- const parsed = typeof issuedAtMs === "number" ? issuedAtMs : Number(issuedAtMs);
59
- const nowMs = options.nowMs ?? Date.now();
60
- const futureToleranceMs = options.futureToleranceMs ?? 30_000;
61
- if (!Number.isFinite(parsed) ||
62
- parsed <= 0 ||
63
- nowMs - parsed > options.ttlMs ||
64
- parsed - nowMs > futureToleranceMs) {
65
- throw new ProviderChoiceTokenError("stale", "Provider choice token is stale.");
66
- }
67
- return parsed;
68
- }
69
- function choiceEncryptionKey(secret) {
70
- return createHash("sha256").update(secret).digest();
71
- }
72
- function signProviderChoiceTokenBody(body, secret) {
73
- return createHmac("sha256", secret).update(body).digest("base64url");
74
- }
@@ -1,9 +0,0 @@
1
- export declare const CHOICE_WORDLIST_SIZE = 1296;
2
- export declare const CHOICE_WORD_ENTROPY_BITS: number;
3
- export declare const STANDARD_CHOICE_WORD_COUNT = 4;
4
- export declare const HIGH_CHOICE_WORD_COUNT = 5;
5
- export declare const STANDARD_CHOICE_ENTROPY_BITS: number;
6
- export declare const HIGH_CHOICE_ENTROPY_BITS: number;
7
- export declare const EFF_SHORT_WORDLIST_2: readonly string[];
8
- export declare function choiceWordAt(index: number): string;
9
- export declare function isChoiceWord(value: string): boolean;
@@ -1,27 +0,0 @@
1
- import type { CredentialContext, EnvContext, ProviderChoiceConsumeMode, ProviderChoiceContext, ProviderRequestContext, ProviderRuntimeState } from "../types.js";
2
- export declare const PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV = "APIFUSE__PROVIDER_RUNTIME__CHOICE_TOKEN_MASTER_SECRET";
3
- export type ProviderChoiceTelemetryEvent = {
4
- readonly providerId: string;
5
- readonly purpose: string;
6
- readonly operation: "parse" | "consume";
7
- readonly format: "word" | "legacy";
8
- readonly outcome: "success" | "not-found" | "invalid" | "unsupported" | "error";
9
- readonly consumeMode: ProviderChoiceConsumeMode;
10
- readonly consumed: boolean;
11
- readonly replay: boolean;
12
- };
13
- export type CreateProviderChoiceContextOptions = {
14
- readonly providerId: string;
15
- readonly env?: EnvContext;
16
- readonly request?: ProviderRequestContext;
17
- readonly credential?: CredentialContext;
18
- readonly state?: ProviderRuntimeState;
19
- readonly masterSecret?: string;
20
- readonly kid?: string;
21
- /** Receives allowlisted metadata only; token and payload values are never included. */
22
- readonly onTelemetry?: (event: ProviderChoiceTelemetryEvent) => void;
23
- };
24
- export declare function createProviderChoiceContext(options: CreateProviderChoiceContextOptions): ProviderChoiceContext;
25
- export declare function createTestProviderChoiceContext(options: Omit<CreateProviderChoiceContextOptions, "masterSecret"> & {
26
- readonly masterSecret?: string;
27
- }): ProviderChoiceContext;