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

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 (150) hide show
  1. package/AUTHORING.md +25 -0
  2. package/CHANGELOG.md +8 -0
  3. package/README.md +18 -2
  4. package/bin/apifuse-dev.ts +2 -1
  5. package/bin/apifuse-pack-types.ts +49 -0
  6. package/bin/apifuse-perf.ts +41 -4
  7. package/bin/apifuse-record.ts +9 -2
  8. package/dist/cli/templates/provider/README.md.tpl +15 -3
  9. package/dist/config/loader.d.ts +7 -0
  10. package/dist/config/loader.js +43 -18
  11. package/dist/declaration-validation.d.ts +2 -1
  12. package/dist/declaration-validation.js +39 -0
  13. package/dist/define.d.ts +4 -4
  14. package/dist/define.js +25 -1
  15. package/dist/engine.d.ts +16 -0
  16. package/dist/engine.js +59 -3
  17. package/dist/error-resolution.js +13 -0
  18. package/dist/index.d.ts +7 -4
  19. package/dist/index.js +4 -2
  20. package/dist/lint.js +38 -17
  21. package/dist/provider.d.ts +3 -3
  22. package/dist/provider.js +2 -2
  23. package/dist/runtime/bounded-expiring-map.d.ts +21 -0
  24. package/dist/runtime/bounded-expiring-map.js +64 -0
  25. package/dist/runtime/browser.js +2 -1
  26. package/dist/runtime/cache.d.ts +2 -0
  27. package/dist/runtime/cache.js +11 -3
  28. package/dist/runtime/diagnostic-env-values.d.ts +5 -0
  29. package/dist/runtime/diagnostic-env-values.js +57 -0
  30. package/dist/runtime/diagnostic-env.d.ts +11 -0
  31. package/dist/runtime/diagnostic-env.js +35 -0
  32. package/dist/runtime/diagnostic-redactor.d.ts +49 -0
  33. package/dist/runtime/diagnostic-redactor.js +569 -0
  34. package/dist/runtime/egress-lease.d.ts +65 -0
  35. package/dist/runtime/egress-lease.js +236 -0
  36. package/dist/runtime/env.js +2 -1
  37. package/dist/runtime/http.js +1 -0
  38. package/dist/runtime/instrumentation.js +3 -0
  39. package/dist/runtime/ocr.js +2 -1
  40. package/dist/runtime/otlp.js +5 -4
  41. package/dist/runtime/proxy-nodemaven.js +3 -2
  42. package/dist/runtime/proxy-retry-policy.d.ts +4 -1
  43. package/dist/runtime/proxy-retry-policy.js +11 -2
  44. package/dist/runtime/request-options.d.ts +7 -0
  45. package/dist/runtime/request-options.js +62 -22
  46. package/dist/runtime/request-telemetry.d.ts +7 -9
  47. package/dist/runtime/resolver-config.d.ts +7 -0
  48. package/dist/runtime/resolver-config.js +8 -0
  49. package/dist/runtime/resolver-public.d.ts +3 -1
  50. package/dist/runtime/resolver-public.js +1 -1
  51. package/dist/runtime/resolver-shared.d.ts +9 -0
  52. package/dist/runtime/resolver-shared.js +57 -0
  53. package/dist/runtime/resolver-telemetry.d.ts +134 -0
  54. package/dist/runtime/resolver-telemetry.js +204 -0
  55. package/dist/runtime/resolver-usage.d.ts +24 -0
  56. package/dist/runtime/resolver-usage.js +60 -0
  57. package/dist/runtime/resolver-vendors/bindings.d.ts +5 -0
  58. package/dist/runtime/resolver-vendors/bindings.js +5 -0
  59. package/dist/runtime/resolver-vendors/browser.js +0 -1
  60. package/dist/runtime/resolver-vendors/capsolver.d.ts +2 -2
  61. package/dist/runtime/resolver-vendors/capsolver.js +21 -9
  62. package/dist/runtime/resolver-vendors/hypersolutions.d.ts +27 -0
  63. package/dist/runtime/resolver-vendors/hypersolutions.js +369 -0
  64. package/dist/runtime/resolver-vendors/twocaptcha.d.ts +2 -2
  65. package/dist/runtime/resolver-vendors/twocaptcha.js +26 -10
  66. package/dist/runtime/resolver-vendors/types.d.ts +35 -5
  67. package/dist/runtime/resolver-vendors/types.js +20 -10
  68. package/dist/runtime/resolver.d.ts +5 -1
  69. package/dist/runtime/resolver.js +400 -111
  70. package/dist/runtime/stealth-akamai-sbsd.d.ts +54 -0
  71. package/dist/runtime/stealth-akamai-sbsd.js +142 -0
  72. package/dist/runtime/stealth.js +547 -53
  73. package/dist/runtime/stt.js +3 -2
  74. package/dist/runtime/trace.d.ts +5 -0
  75. package/dist/runtime/trace.js +115 -20
  76. package/dist/schema.d.ts +20 -0
  77. package/dist/schema.js +72 -2
  78. package/dist/server/index.d.ts +2 -0
  79. package/dist/server/index.js +1 -0
  80. package/dist/server/self-test-redaction.js +8 -33
  81. package/dist/server/self-test.d.ts +4 -0
  82. package/dist/server/self-test.js +10 -1
  83. package/dist/server/sensitive-values.d.ts +26 -0
  84. package/dist/server/sensitive-values.js +131 -0
  85. package/dist/server/serve-implementation.d.ts +7 -1
  86. package/dist/server/serve-implementation.js +394 -125
  87. package/dist/server/trace-output.d.ts +2 -1
  88. package/dist/server/trace-output.js +46 -14
  89. package/dist/server/types.d.ts +6 -0
  90. package/dist/server/types.js +14 -0
  91. package/dist/stealth/profiles.d.ts +7 -1
  92. package/dist/stealth/profiles.js +9 -0
  93. package/dist/testing/run.js +6 -0
  94. package/dist/trace-sanitization.d.ts +4 -3
  95. package/dist/trace-sanitization.js +45 -22
  96. package/dist/types.d.ts +106 -10
  97. package/package.json +2 -1
  98. package/src/cli/templates/provider/README.md.tpl +15 -3
  99. package/src/config/loader.ts +47 -18
  100. package/src/declaration-validation.ts +47 -1
  101. package/src/define.ts +45 -3
  102. package/src/engine.ts +78 -3
  103. package/src/error-resolution.ts +13 -0
  104. package/src/index.ts +34 -1
  105. package/src/lint.ts +44 -18
  106. package/src/provider.ts +10 -0
  107. package/src/runtime/bounded-expiring-map.ts +67 -0
  108. package/src/runtime/browser.ts +2 -1
  109. package/src/runtime/cache.ts +13 -3
  110. package/src/runtime/diagnostic-env-values.ts +56 -0
  111. package/src/runtime/diagnostic-env.ts +55 -0
  112. package/src/runtime/diagnostic-redactor.ts +662 -0
  113. package/src/runtime/egress-lease.ts +341 -0
  114. package/src/runtime/env.ts +2 -1
  115. package/src/runtime/http.ts +1 -0
  116. package/src/runtime/instrumentation.ts +3 -0
  117. package/src/runtime/ocr.ts +2 -1
  118. package/src/runtime/otlp.ts +5 -4
  119. package/src/runtime/proxy-nodemaven.ts +3 -2
  120. package/src/runtime/proxy-retry-policy.ts +12 -1
  121. package/src/runtime/request-options.ts +76 -28
  122. package/src/runtime/request-telemetry.ts +5 -7
  123. package/src/runtime/resolver-config.ts +12 -0
  124. package/src/runtime/resolver-public.ts +21 -0
  125. package/src/runtime/resolver-shared.ts +75 -0
  126. package/src/runtime/resolver-telemetry.ts +362 -0
  127. package/src/runtime/resolver-usage.ts +96 -0
  128. package/src/runtime/resolver-vendors/bindings.ts +5 -0
  129. package/src/runtime/resolver-vendors/browser.ts +0 -1
  130. package/src/runtime/resolver-vendors/capsolver.ts +22 -3
  131. package/src/runtime/resolver-vendors/hypersolutions.ts +480 -0
  132. package/src/runtime/resolver-vendors/twocaptcha.ts +26 -12
  133. package/src/runtime/resolver-vendors/types.ts +60 -12
  134. package/src/runtime/resolver.ts +468 -126
  135. package/src/runtime/stealth-akamai-sbsd.ts +207 -0
  136. package/src/runtime/stealth.ts +727 -95
  137. package/src/runtime/stt.ts +3 -2
  138. package/src/runtime/trace.ts +168 -26
  139. package/src/schema.ts +80 -2
  140. package/src/server/index.ts +21 -0
  141. package/src/server/self-test-redaction.ts +10 -40
  142. package/src/server/self-test.ts +13 -0
  143. package/src/server/sensitive-values.ts +200 -0
  144. package/src/server/serve-implementation.ts +561 -136
  145. package/src/server/trace-output.ts +77 -17
  146. package/src/server/types.ts +14 -0
  147. package/src/stealth/profiles.ts +10 -0
  148. package/src/testing/run.ts +6 -0
  149. package/src/trace-sanitization.ts +70 -23
  150. package/src/types.ts +124 -19
@@ -0,0 +1,236 @@
1
+ import { readDiagnosticEnv } from "./diagnostic-env.js";
2
+ import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
3
+ import { SDKError } from "../errors.js";
4
+ /**
5
+ * AES-256-GCM key for the engine ceremony egress lease handle (ADR-0010 v1.1).
6
+ * Engine host only: the `APIFUSE__ENGINE__` family is rejected from provider
7
+ * secrets and filtered from provider env projections. Supply at least 32 random
8
+ * bytes (for example `openssl rand -base64 32`). Rotating the key fails
9
+ * verification of every outstanding handle (`EGRESS_LEASE_INVALID`, 409): the
10
+ * affected ceremonies restart; they do not expire.
11
+ */
12
+ export const APIFUSE__ENGINE__CEREMONY_LEASE_KEY = "APIFUSE__ENGINE__CEREMONY_LEASE_KEY";
13
+ /** Internal option key shared only by the server assembler and stealth runtime. */
14
+ export const ENGINE_CEREMONY_EGRESS_LEASE = Symbol.for("@apifuse/provider-sdk/engine-ceremony-egress-lease");
15
+ const HANDLE_VERSION = 1;
16
+ const HANDLE_MAX_BYTES = 16_384;
17
+ const AEAD_NONCE_BYTES = 12;
18
+ const AEAD_TAG_BYTES = 16;
19
+ const AEAD_ALGORITHM = "aes-256-gcm";
20
+ const AEAD_KEY_DOMAIN = "apifuse:ceremony-egress-lease:v1\0";
21
+ const LEASE_KEY_MIN_BYTES = 32;
22
+ /** The caller presented a handle this engine cannot verify for this ceremony (409). */
23
+ function invalidLease() {
24
+ throw new SDKError("The engine ceremony egress lease is invalid", {
25
+ code: "EGRESS_LEASE_INVALID",
26
+ fix: "Restart the authentication ceremony to obtain a new engine-owned egress lease.",
27
+ });
28
+ }
29
+ /** The engine violated its own binding invariant (500); never attributable to the caller. */
30
+ function bindingFault(message) {
31
+ throw new SDKError(message, {
32
+ code: "EGRESS_LEASE_BINDING_INVALID",
33
+ fix: "This is an SDK or provider-runtime fault; the ceremony lease cannot bind the selected egress.",
34
+ });
35
+ }
36
+ function encryptionKey(key) {
37
+ return createHash("sha256").update(AEAD_KEY_DOMAIN, "utf8").update(key, "utf8").digest();
38
+ }
39
+ /** Fixed field order and JSON.stringify's compact form are the canonical AAD encoding. */
40
+ function scopeAad(scope) {
41
+ return Buffer.from(JSON.stringify({
42
+ version: HANDLE_VERSION,
43
+ tenantId: scope.tenantId,
44
+ providerId: scope.providerId,
45
+ flowId: scope.flowId,
46
+ affinityKey: scope.affinityKey,
47
+ }), "utf8");
48
+ }
49
+ function encodeHandle(payload, key, scope) {
50
+ const nonce = randomBytes(AEAD_NONCE_BYTES);
51
+ const cipher = createCipheriv(AEAD_ALGORITHM, encryptionKey(key), nonce, {
52
+ authTagLength: AEAD_TAG_BYTES,
53
+ });
54
+ cipher.setAAD(scopeAad(scope));
55
+ const ciphertext = Buffer.concat([
56
+ cipher.update(JSON.stringify(payload), "utf8"),
57
+ cipher.final(),
58
+ ]);
59
+ return [
60
+ `v${HANDLE_VERSION}`,
61
+ nonce.toString("base64url"),
62
+ ciphertext.toString("base64url"),
63
+ cipher.getAuthTag().toString("base64url"),
64
+ ].join(".");
65
+ }
66
+ function isFiniteInteger(value) {
67
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
68
+ }
69
+ function isPositiveFinite(value) {
70
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
71
+ }
72
+ function decodeHandle(handle, key, scope) {
73
+ if (Buffer.byteLength(handle, "utf8") > HANDLE_MAX_BYTES)
74
+ return invalidLease();
75
+ const parts = handle.split(".");
76
+ if (parts.length !== 4 ||
77
+ parts[0] !== `v${HANDLE_VERSION}` ||
78
+ parts.slice(1).some((part) => !/^[A-Za-z0-9_-]+$/u.test(part))) {
79
+ return invalidLease();
80
+ }
81
+ let parsed;
82
+ try {
83
+ const nonce = Buffer.from(parts[1], "base64url");
84
+ const ciphertext = Buffer.from(parts[2], "base64url");
85
+ const tag = Buffer.from(parts[3], "base64url");
86
+ if (nonce.byteLength !== AEAD_NONCE_BYTES ||
87
+ tag.byteLength !== AEAD_TAG_BYTES ||
88
+ ciphertext.byteLength === 0) {
89
+ return invalidLease();
90
+ }
91
+ const decipher = createDecipheriv(AEAD_ALGORITHM, encryptionKey(key), nonce, {
92
+ authTagLength: AEAD_TAG_BYTES,
93
+ });
94
+ decipher.setAAD(scopeAad(scope));
95
+ decipher.setAuthTag(tag);
96
+ const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
97
+ parsed = JSON.parse(plaintext.toString("utf8"));
98
+ }
99
+ catch {
100
+ return invalidLease();
101
+ }
102
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
103
+ return invalidLease();
104
+ const candidate = parsed;
105
+ if (candidate.version !== HANDLE_VERSION ||
106
+ (candidate.vendor !== "smartproxy" && candidate.vendor !== "nodemaven") ||
107
+ typeof candidate.proxyUrl !== "string" ||
108
+ !candidate.proxyUrl ||
109
+ !isFiniteInteger(candidate.poolIndex) ||
110
+ typeof candidate.affinityKey !== "string" ||
111
+ !candidate.affinityKey ||
112
+ !isFiniteInteger(candidate.refreshEpoch) ||
113
+ typeof candidate.tenantId !== "string" ||
114
+ !candidate.tenantId ||
115
+ typeof candidate.providerId !== "string" ||
116
+ !candidate.providerId ||
117
+ typeof candidate.flowId !== "string" ||
118
+ !candidate.flowId ||
119
+ !isFiniteInteger(candidate.mintedAtMs) ||
120
+ !isFiniteInteger(candidate.expiresAtMs) ||
121
+ candidate.expiresAtMs <= candidate.mintedAtMs ||
122
+ !isPositiveFinite(candidate.lifetimeMinutes)) {
123
+ return invalidLease();
124
+ }
125
+ try {
126
+ const proxy = new URL(candidate.proxyUrl);
127
+ if (proxy.protocol !== "http:" && proxy.protocol !== "socks5:")
128
+ return invalidLease();
129
+ }
130
+ catch {
131
+ return invalidLease();
132
+ }
133
+ return candidate;
134
+ }
135
+ function bindingOf(payload) {
136
+ return {
137
+ vendor: payload.vendor,
138
+ proxyUrl: payload.proxyUrl,
139
+ poolIndex: payload.poolIndex,
140
+ affinityKey: payload.affinityKey,
141
+ refreshEpoch: payload.refreshEpoch,
142
+ lifetimeMinutes: payload.lifetimeMinutes,
143
+ };
144
+ }
145
+ export function createCeremonyEgressLeaseRuntime(options) {
146
+ const environment = options.environment ?? process.env;
147
+ const now = options.now ?? Date.now;
148
+ if (typeof options.tenantId !== "string" || !options.tenantId.trim()) {
149
+ throw new SDKError("A tenant is required for an engine ceremony egress lease", {
150
+ code: "EGRESS_LEASE_INVALID",
151
+ fix: "Restart the authentication ceremony with its tenant identity.",
152
+ });
153
+ }
154
+ const scope = {
155
+ tenantId: options.tenantId,
156
+ providerId: options.providerId,
157
+ flowId: options.flowId,
158
+ affinityKey: options.affinityKey,
159
+ };
160
+ const key = readDiagnosticEnv(APIFUSE__ENGINE__CEREMONY_LEASE_KEY, environment)?.trim();
161
+ if (!key) {
162
+ throw new SDKError("The engine ceremony egress lease key is not configured", {
163
+ code: "EGRESS_LEASE_KEY_MISSING",
164
+ fix: `Configure ${APIFUSE__ENGINE__CEREMONY_LEASE_KEY} in the engine host.`,
165
+ });
166
+ }
167
+ // The key is the whole authenticity boundary of a handle (a verified handle's proxyUrl
168
+ // becomes the ceremony egress), so the documented minimum is enforced, not advisory.
169
+ if (Buffer.byteLength(key, "utf8") < LEASE_KEY_MIN_BYTES) {
170
+ throw new SDKError(`The engine ceremony egress lease key is shorter than ${LEASE_KEY_MIN_BYTES} bytes`, {
171
+ code: "EGRESS_LEASE_KEY_WEAK",
172
+ fix: `Set ${APIFUSE__ENGINE__CEREMONY_LEASE_KEY} to at least ${LEASE_KEY_MIN_BYTES} random bytes (for example \`openssl rand -base64 32\`).`,
173
+ });
174
+ }
175
+ let payload;
176
+ let currentHandle;
177
+ let generation = 0;
178
+ const dropBinding = () => {
179
+ if (!payload)
180
+ return false;
181
+ payload = undefined;
182
+ currentHandle = undefined;
183
+ generation += 1;
184
+ return true;
185
+ };
186
+ const dropExpiredBinding = () => payload !== undefined && now() >= payload.expiresAtMs ? dropBinding() : false;
187
+ if (options.handle !== undefined) {
188
+ options.onHandle?.(options.handle);
189
+ // Scope mismatch fails GCM authentication because scope is AAD; there is
190
+ // deliberately no post-decrypt scope comparison fallback.
191
+ payload = decodeHandle(options.handle, key, scope);
192
+ currentHandle = options.handle;
193
+ // An expired but authentic handle is not a fault: the ceremony continues on a
194
+ // freshly selected endpoint and the next turn receives the new handle.
195
+ dropExpiredBinding();
196
+ }
197
+ return {
198
+ get binding() {
199
+ return payload ? bindingOf(payload) : undefined;
200
+ },
201
+ dropExpiredBinding,
202
+ dropBinding,
203
+ get generation() {
204
+ return generation;
205
+ },
206
+ bind(binding) {
207
+ if (payload) {
208
+ // The stealth runtime reuses the bound endpoint while a binding exists and
209
+ // never rebinds it; a second bind is an engine invariant break.
210
+ return bindingFault("The ceremony egress lease is already bound");
211
+ }
212
+ if (binding.affinityKey !== options.affinityKey ||
213
+ !binding.proxyUrl ||
214
+ (binding.vendor !== "smartproxy" && binding.vendor !== "nodemaven") ||
215
+ !isFiniteInteger(binding.poolIndex) ||
216
+ !isPositiveFinite(binding.lifetimeMinutes)) {
217
+ return bindingFault("The selected egress cannot be bound to the ceremony lease");
218
+ }
219
+ const mintedAtMs = now();
220
+ payload = {
221
+ version: HANDLE_VERSION,
222
+ tenantId: scope.tenantId,
223
+ providerId: options.providerId,
224
+ flowId: options.flowId,
225
+ ...binding,
226
+ mintedAtMs,
227
+ expiresAtMs: mintedAtMs + binding.lifetimeMinutes * 60_000,
228
+ };
229
+ currentHandle = encodeHandle(payload, key, scope);
230
+ options.onHandle?.(currentHandle);
231
+ },
232
+ handle() {
233
+ return currentHandle;
234
+ },
235
+ };
236
+ }
@@ -1,3 +1,4 @@
1
+ import { readDiagnosticEnv } from "./diagnostic-env.js";
1
2
  import { isEngineOwnedEnvName } from "../engine.js";
2
3
  /**
3
4
  * A mixed-case spelling of an engine-owned name is an alias: on Windows it resolves to the
@@ -16,7 +17,7 @@ export function createEnvContext(allowedKeys) {
16
17
  if (isEngineOwnedEnvAlias(key)) {
17
18
  return undefined;
18
19
  }
19
- return process.env[key];
20
+ return readDiagnosticEnv(key);
20
21
  },
21
22
  };
22
23
  }
@@ -498,6 +498,7 @@ async function resolveNativeProxy(options, clientOptions, warn, proxyAttemptOffs
498
498
  // supported here, so a socks5 policy fails loudly rather than downgrading.
499
499
  transportProtocols: ["http"],
500
500
  telemetry: clientOptions.telemetry,
501
+ engineCredentials: clientOptions.engineCredentials,
501
502
  });
502
503
  if (resolvedProxy.shouldWarn) {
503
504
  warn("[provider-sdk] Provider requested proxy routing, but no proxy URL was configured. Continuing without proxy.");
@@ -1,3 +1,4 @@
1
+ import { registerDiagnosticSensitiveValues } from "./diagnostic-redactor.js";
1
2
  import { readableBytes, readableLines, readableTextChunks } from "../stream.js";
2
3
  import { parseHttpRequestInvocation, isSensitiveKey, redactSensitiveError, redactSensitiveText, redactUrlQueryParams, requestOptionsFromHttpInvocation, serializeRequestUrl, } from "./request-options.js";
3
4
  import { createTraceContext, getTraceRecorder, } from "./trace.js";
@@ -381,6 +382,7 @@ function wrapStealthRedirects(redirects, trace) {
381
382
  run(...args) {
382
383
  const diagnosticArgs = [args[0].url, args[0]];
383
384
  const diagnostics = snapshotRequestDiagnostics("stealth", "fetch", diagnosticArgs);
385
+ registerDiagnosticSensitiveValues(trace, diagnostics.sensitiveValues);
384
386
  let result;
385
387
  try {
386
388
  result = redirects.run(...args);
@@ -511,6 +513,7 @@ function wrapNamespace(namespace, target, trace, shouldInstrument) {
511
513
  return Reflect.apply(value, namespaceTarget, args);
512
514
  }
513
515
  const requestDiagnostics = snapshotRequestDiagnostics(namespace, methodName, args);
516
+ registerDiagnosticSensitiveValues(trace, requestDiagnostics.sensitiveValues);
514
517
  // Invoke first and decide by the RETURN VALUE. `runSpan` always
515
518
  // returns a Promise, so unconditionally span-wrapping every member
516
519
  // silently rewrote synchronous contracts: `ctx.state.namespace()`
@@ -1,3 +1,4 @@
1
+ import { readDiagnosticEnv } from "./diagnostic-env.js";
1
2
  import { ProviderError, TransportError } from "../errors.js";
2
3
  import { CLOUDFLARE_ACCOUNT_ID_ENV } from "./stt.js";
3
4
  import { createTimeoutController, isTimeoutLikeError } from "./timeout.js";
@@ -55,7 +56,7 @@ export function createUnsupportedOcrClient(reason) {
55
56
  });
56
57
  }
57
58
  function normalizedEnvValue(env, key) {
58
- const value = env[key]?.trim();
59
+ const value = readDiagnosticEnv(key, env)?.trim();
59
60
  return value ? value : undefined;
60
61
  }
61
62
  export function createOcrClientFromEnv(config, env = process.env) {
@@ -1,3 +1,4 @@
1
+ import { readDiagnosticEnv } from "./diagnostic-env.js";
1
2
  import { AsyncResource } from "node:async_hooks";
2
3
  export const OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT";
3
4
  export const OTEL_EXPORTER_OTLP_ENDPOINT = "OTEL_EXPORTER_OTLP_ENDPOINT";
@@ -118,12 +119,12 @@ export function resolveOTLPExportOptions(explicit, env = process.env) {
118
119
  { source: EXPLICIT_ENDPOINT_SOURCE, value: explicit.endpoint, appendPath: false },
119
120
  {
120
121
  source: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
121
- value: env[OTEL_EXPORTER_OTLP_TRACES_ENDPOINT],
122
+ value: readDiagnosticEnv(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, env),
122
123
  appendPath: false,
123
124
  },
124
125
  {
125
126
  source: OTEL_EXPORTER_OTLP_ENDPOINT,
126
- value: env[OTEL_EXPORTER_OTLP_ENDPOINT],
127
+ value: readDiagnosticEnv(OTEL_EXPORTER_OTLP_ENDPOINT, env),
127
128
  appendPath: true,
128
129
  },
129
130
  ];
@@ -137,9 +138,9 @@ export function resolveOTLPExportOptions(explicit, env = process.env) {
137
138
  return { status: "invalid", source: candidate.source, reason: parsed.reason };
138
139
  }
139
140
  const endpoint = candidate.appendPath ? appendTracesPath(parsed.url) : candidate.value;
140
- const tracesHeaders = presentValue(env[OTEL_EXPORTER_OTLP_TRACES_HEADERS]);
141
+ const tracesHeaders = presentValue(readDiagnosticEnv(OTEL_EXPORTER_OTLP_TRACES_HEADERS, env));
141
142
  const headersSource = tracesHeaders !== undefined ? OTEL_EXPORTER_OTLP_TRACES_HEADERS : OTEL_EXPORTER_OTLP_HEADERS;
142
- const envHeaders = parseHeaderList(tracesHeaders ?? presentValue(env[OTEL_EXPORTER_OTLP_HEADERS]));
143
+ const envHeaders = parseHeaderList(tracesHeaders ?? presentValue(readDiagnosticEnv(OTEL_EXPORTER_OTLP_HEADERS, env)));
143
144
  if (!headersAreSendable(envHeaders)) {
144
145
  return { status: "invalid", source: headersSource, reason: INVALID_HEADERS };
145
146
  }
@@ -1,3 +1,4 @@
1
+ import { readDiagnosticEnv } from "./diagnostic-env.js";
1
2
  import { createHash, randomBytes } from "node:crypto";
2
3
  export const NODEMAVEN_USERNAME_ENV = "APIFUSE__PROXY__NODEMAVEN_USERNAME";
3
4
  export const NODEMAVEN_PASSWORD_ENV = "APIFUSE__PROXY__NODEMAVEN_PASSWORD";
@@ -25,10 +26,10 @@ export function hasNodemavenCredentials() {
25
26
  return Boolean(readNodemavenUsername() && readNodemavenPassword());
26
27
  }
27
28
  function readNodemavenUsername() {
28
- return process.env[NODEMAVEN_USERNAME_ENV]?.trim() || undefined;
29
+ return readDiagnosticEnv(NODEMAVEN_USERNAME_ENV)?.trim() || undefined;
29
30
  }
30
31
  function readNodemavenPassword() {
31
- return process.env[NODEMAVEN_PASSWORD_ENV]?.trim() || undefined;
32
+ return readDiagnosticEnv(NODEMAVEN_PASSWORD_ENV)?.trim() || undefined;
32
33
  }
33
34
  function resolveNodemavenFilter(value) {
34
35
  const raw = value?.trim().toLowerCase();
@@ -21,7 +21,10 @@ export declare function normalizeProxyTransportRetryOptions(retry: RequestOption
21
21
  label?: RetryPolicyLabel;
22
22
  }): NormalizedProxyTransportRetryOptions | undefined;
23
23
  export declare function validateUnsafeProxyTransportRetryMethods(options: NormalizedProxyTransportRetryOptions, label?: RetryPolicyLabel): void;
24
- export declare function isProxyTransportRetryMethod(method: HttpMethod | string, options: NormalizedProxyTransportRetryOptions): boolean;
24
+ export declare function isProxyTransportRetryMethod(method: HttpMethod | string, options: NormalizedProxyTransportRetryOptions, request?: {
25
+ readonly body?: unknown;
26
+ readonly headers?: Readonly<Record<string, string | readonly string[] | undefined>>;
27
+ }): boolean;
25
28
  export declare function shouldRetryProxyTransportError(error: unknown, options: NormalizedProxyTransportRetryOptions): boolean;
26
29
  export declare function shouldRetryProxyTransportAttempt(input: {
27
30
  error: unknown;
@@ -1,5 +1,6 @@
1
1
  import { ProviderError, TransportError } from "../errors.js";
2
2
  import { HttpRetryAfterPolicy, HttpRetryDelayStrategy, HttpRetryJitter, HttpRetryPreset, HttpRetryUnsafeMethodPolicy, } from "../types.js";
3
+ import { isSensitiveKey } from "./request-options.js";
3
4
  export const DEFAULT_PROXY_TRANSPORT_RETRY_METHODS = ["GET", "HEAD", "OPTIONS"];
4
5
  export const DEFAULT_PROXY_TRANSPORT_RETRY_ERROR_CODES = [
5
6
  "transport_network_error",
@@ -250,10 +251,18 @@ export function validateUnsafeProxyTransportRetryMethods(options, label = "Proxy
250
251
  return;
251
252
  throw new ProviderError(`${label} retry methods include unsafe method(s): ${unsafeMethods.join(", ")}`, { code: "retry_unsafe_method" });
252
253
  }
253
- export function isProxyTransportRetryMethod(method, options) {
254
- return options.methods
254
+ export function isProxyTransportRetryMethod(method, options, request) {
255
+ const methodAllowed = options.methods
255
256
  .map((allowedMethod) => allowedMethod.toUpperCase())
256
257
  .includes(method.toUpperCase());
258
+ if (!methodAllowed || request === undefined)
259
+ return methodAllowed;
260
+ if (request.body !== undefined)
261
+ return false;
262
+ return !Object.keys(request.headers ?? {}).some((name) => {
263
+ const normalized = name.toLowerCase();
264
+ return normalized === "cookie" || normalized === "proxy-authorization" || isSensitiveKey(name);
265
+ });
257
266
  }
258
267
  export function shouldRetryProxyTransportError(error, options) {
259
268
  const code = retryErrorCode(error);
@@ -51,6 +51,13 @@ export declare function redactUrlQueryParams(url: string, sensitiveParamNames: r
51
51
  * separate, structurally redacted URL for diagnostics and observability.
52
52
  */
53
53
  export declare function serializeRequestUrl(url: string, params?: RequestParams, sensitiveParams?: Record<string, string>): SerializedRequestUrl;
54
+ export declare function normalizePercentEscapes(value: string): string;
55
+ export type SensitiveValueVariant = {
56
+ value: string;
57
+ requiresTokenBoundary: boolean;
58
+ caseInsensitive: boolean;
59
+ };
60
+ export declare function allSensitiveValueVariants(sensitiveValues: readonly string[]): SensitiveValueVariant[];
54
61
  /** Scrubs raw and URL-encoded secret values from diagnostic text. */
55
62
  export declare function redactSensitiveText(text: string, sensitiveValues: readonly string[], requestUrl?: string, redactedUrl?: string): string;
56
63
  /**
@@ -108,13 +108,33 @@ export function serializeRequestUrl(url, params, sensitiveParams) {
108
108
  sensitiveValues: [...sensitiveValues],
109
109
  };
110
110
  }
111
- const MIN_UNSCOPED_SENSITIVE_VALUE_LENGTH = 4;
112
- function normalizePercentEscapes(value) {
111
+ const MIN_UNSCOPED_SENSITIVE_VALUE_LENGTH = 8;
112
+ export function normalizePercentEscapes(value) {
113
113
  return value.replace(/%[\da-f]{2}/gi, (percentEscape) => percentEscape.toUpperCase());
114
114
  }
115
115
  function sensitiveValueVariants(value) {
116
116
  if (!value || value === REDACTED_QUERY_VALUE)
117
117
  return [];
118
+ // Owner-visible policy: short credentials are literal, case-sensitive tokens only.
119
+ // Encoded short needles collide with benign cursors and ordinary diagnostic text.
120
+ if (value.length < 8)
121
+ return [value];
122
+ // These ASCII characters are identical under NFC, JSON, URI-component and
123
+ // form encoding. Avoid constructing those identical variants for common tokens.
124
+ if (/^[A-Za-z0-9._*-]+$/.test(value)) {
125
+ const base64 = Buffer.from(value, "utf8").toString("base64");
126
+ const base64Url = base64.replaceAll("+", "-").replaceAll("/", "_");
127
+ return [
128
+ ...new Set([
129
+ value,
130
+ base64,
131
+ base64.replace(/=+$/, ""),
132
+ base64Url,
133
+ base64Url.replace(/=+$/, ""),
134
+ ]),
135
+ ];
136
+ }
137
+ const normalized = value.normalize("NFC");
118
138
  const formEncoded = new URLSearchParams({ value }).toString().slice("value=".length);
119
139
  let componentEncoded;
120
140
  try {
@@ -125,12 +145,31 @@ function sensitiveValueVariants(value) {
125
145
  // still safe to scrub, and URLSearchParams supplies its well-formed form.
126
146
  componentEncoded = value;
127
147
  }
128
- return [...new Set([value, componentEncoded, formEncoded].map(normalizePercentEscapes))];
129
- }
130
- function allSensitiveValueVariants(sensitiveValues) {
148
+ const base64 = Buffer.from(value, "utf8").toString("base64");
149
+ const base64Url = base64.replaceAll("+", "-").replaceAll("/", "_");
150
+ const jsonEscaped = JSON.stringify(value).slice(1, -1);
151
+ return [
152
+ ...new Set([
153
+ value,
154
+ normalized,
155
+ componentEncoded,
156
+ formEncoded,
157
+ jsonEscaped,
158
+ base64,
159
+ base64.replace(/=+$/, ""),
160
+ base64Url,
161
+ base64Url.replace(/=+$/, ""),
162
+ ].map(normalizePercentEscapes)),
163
+ ];
164
+ }
165
+ export function allSensitiveValueVariants(sensitiveValues) {
131
166
  const variants = new Map();
132
167
  for (const sensitiveValue of sensitiveValues) {
133
168
  const requiresTokenBoundary = sensitiveValue.length < MIN_UNSCOPED_SENSITIVE_VALUE_LENGTH;
169
+ // Long values are unlikely to be ordinary prose, so case folding is worth
170
+ // the extra over-redaction protection. Values of 4-7 characters remain
171
+ // case-sensitive because they commonly occur as words and identifiers.
172
+ const caseInsensitive = sensitiveValue.length >= 8;
134
173
  for (const value of sensitiveValueVariants(sensitiveValue)) {
135
174
  const existing = variants.get(value);
136
175
  variants.set(value, {
@@ -138,6 +177,7 @@ function allSensitiveValueVariants(sensitiveValues) {
138
177
  requiresTokenBoundary: existing
139
178
  ? existing.requiresTokenBoundary && requiresTokenBoundary
140
179
  : requiresTokenBoundary,
180
+ caseInsensitive: existing ? existing.caseInsensitive || caseInsensitive : caseInsensitive,
141
181
  });
142
182
  }
143
183
  }
@@ -147,24 +187,24 @@ function isTokenCharacter(value) {
147
187
  return value !== undefined && /[\p{L}\p{N}]/u.test(value);
148
188
  }
149
189
  function replaceSensitiveVariant(text, variant) {
150
- const pattern = variant.value.replace(/%([\da-f]{2})|[.*+?^${}()|[\]\\]/gi, (match, hex) => {
151
- if (hex) {
152
- return `%${[...String(hex)]
153
- .map((character) => /[a-f]/i.test(character)
154
- ? `[${character.toLowerCase()}${character.toUpperCase()}]`
155
- : character)
156
- .join("")}`;
157
- }
158
- return `\\${match}`;
159
- });
160
- return text.replace(new RegExp(pattern, "gu"), (match, offset, source) => {
161
- const end = offset + match.length;
190
+ const normalized = normalizePercentEscapes(text);
191
+ const comparable = variant.caseInsensitive ? normalized.toLowerCase() : text;
192
+ const needle = variant.caseInsensitive ? variant.value.toLowerCase() : variant.value;
193
+ let cursor = 0;
194
+ let output = "";
195
+ for (let index = comparable.indexOf(needle); index !== -1; index = comparable.indexOf(needle, index + 1)) {
196
+ const end = index + needle.length;
162
197
  if (variant.requiresTokenBoundary &&
163
- (isTokenCharacter(source[offset - 1]) || isTokenCharacter(source[end]))) {
164
- return match;
165
- }
166
- return REDACTED_QUERY_VALUE;
167
- });
198
+ (isTokenCharacter(comparable[index - 1]) || isTokenCharacter(comparable[end])))
199
+ continue;
200
+ // Unicode folding can change offsets; suppress the field instead of slicing a secret.
201
+ if (comparable.length !== text.length)
202
+ return REDACTED_QUERY_VALUE;
203
+ output += text.slice(cursor, index) + REDACTED_QUERY_VALUE;
204
+ cursor = end;
205
+ index = end - 1;
206
+ }
207
+ return cursor ? output + text.slice(cursor) : text;
168
208
  }
169
209
  /** Scrubs raw and URL-encoded secret values from diagnostic text. */
170
210
  export function redactSensitiveText(text, sensitiveValues, requestUrl, redactedUrl) {
@@ -1,5 +1,6 @@
1
1
  import type { ProxyTelemetrySink } from "../config/loader.js";
2
2
  import type { ProxyTelemetryLogPayload } from "./proxy-telemetry.js";
3
+ import type { ResolverTelemetryLogPayload } from "./resolver-telemetry.js";
3
4
  import type { Span, TraceContext } from "./trace.js";
4
5
  export declare const PROVIDER_TELEMETRY_HEADER = "X-ApiFuse-Provider-Telemetry";
5
6
  export type TelemetryKey = "proxy" | "resolver" | "http" | "stealth" | "native" | "browser" | "ocr" | "stt" | "cache" | "state" | "events";
@@ -21,17 +22,13 @@ export declare function tenantOpaqueKeys(value: string[]): TenantOpaqueKeys;
21
22
  * URLs, hostnames, free text, unknown values, and escape-hatch properties reduce to `never`.
22
23
  */
23
24
  export type GatewayIngestible<T> = 0 extends 1 & T ? never : T extends object ? keyof T extends never ? never : {
24
- [K in keyof T]: 0 extends 1 & T[K] ? never : unknown extends T[K] ? never : [NonNullable<T[K]>] extends [number | boolean | ClosedEnum<string>] ? T[K] : [NonNullable<T[K]>] extends [readonly (infer U)[]] ? 0 extends 1 & U ? never : unknown extends U ? never : [U] extends [
25
- number | boolean | ClosedEnum<string> | GatewayIngestible<U>
26
- ] ? T[K] : never : [NonNullable<T[K]>] extends [object] ? [NonNullable<T[K]>] extends [GatewayIngestible<NonNullable<T[K]>>] ? T[K] : never : never;
25
+ [K in keyof T]: 0 extends 1 & T[K] ? never : unknown extends T[K] ? never : [NonNullable<T[K]>] extends [number | boolean | ClosedEnum<string>] ? T[K] : [NonNullable<T[K]>] extends [readonly (infer U)[]] ? 0 extends 1 & U ? never : unknown extends U ? never : [U] extends [number | boolean | ClosedEnum<string> | GatewayIngestible<U>] ? T[K] : never : [NonNullable<T[K]>] extends [object] ? [NonNullable<T[K]>] extends [GatewayIngestible<NonNullable<T[K]>>] ? T[K] : never : never;
27
26
  } : never;
28
27
  /** Compile-time projection for tenant-visible, identity-neutral metadata. */
29
28
  export type TenantNeutral<T> = 0 extends 1 & T ? never : T extends object ? {
30
- [K in keyof T]: K extends `vendor${string}` | "provider" | "engine" | "model" | `${string}Host` ? never : 0 extends 1 & T[K] ? never : unknown extends T[K] ? never : [NonNullable<T[K]>] extends [
31
- string[] & {
32
- readonly __tenantOpaqueCacheKeys: true;
33
- }
34
- ] ? K extends "keys" ? T[K] : never : [NonNullable<T[K]>] extends [number | boolean | ClosedEnum<string>] ? T[K] : [NonNullable<T[K]>] extends [object] ? [NonNullable<T[K]>] extends [TenantNeutral<NonNullable<T[K]>>] ? T[K] : never : never;
29
+ [K in keyof T]: K extends `vendor${string}` | "provider" | "engine" | "model" | `${string}Host` ? never : 0 extends 1 & T[K] ? never : unknown extends T[K] ? never : [NonNullable<T[K]>] extends [string[] & {
30
+ readonly __tenantOpaqueCacheKeys: true;
31
+ }] ? K extends "keys" ? T[K] : never : [NonNullable<T[K]>] extends [number | boolean | ClosedEnum<string>] ? T[K] : [NonNullable<T[K]>] extends [object] ? [NonNullable<T[K]>] extends [TenantNeutral<NonNullable<T[K]>>] ? T[K] : never : never;
35
32
  } : never;
36
33
  export interface SpanIndex {
37
34
  readonly spans: readonly Span[];
@@ -46,7 +43,8 @@ export interface TelemetryContributor<Log extends object, Header extends object>
46
43
  }
47
44
  export type RequestTelemetryLogPayload = {
48
45
  proxy?: ProxyTelemetryLogPayload;
49
- } & Partial<Record<Exclude<TelemetryKey, "proxy">, object>>;
46
+ resolver?: ResolverTelemetryLogPayload;
47
+ } & Partial<Record<Exclude<TelemetryKey, "proxy" | "resolver">, object>>;
50
48
  /**
51
49
  * Bounded structural defence in depth against casts and accidental free text.
52
50
  * Closed-enum brands are erased at runtime; the type-level guard is the contract,
@@ -1,6 +1,13 @@
1
+ import type { ProviderChallengeKind } from "../types.js";
1
2
  export declare const APIFUSE__RESOLVER__2CAPTCHA__API_KEY = "APIFUSE__RESOLVER__2CAPTCHA__API_KEY";
2
3
  export declare const APIFUSE__RESOLVER__CAPSOLVER__API_KEY = "APIFUSE__RESOLVER__CAPSOLVER__API_KEY";
3
4
  export declare const APIFUSE__RESOLVER__CAPMONSTER__API_KEY = "APIFUSE__RESOLVER__CAPMONSTER__API_KEY";
5
+ export declare const APIFUSE__RESOLVER__HYPERSOLUTIONS__API_KEY = "APIFUSE__RESOLVER__HYPERSOLUTIONS__API_KEY";
4
6
  export declare const APIFUSE__RESOLVER__TIMEOUT_MS = "APIFUSE__RESOLVER__TIMEOUT_MS";
5
7
  export declare const APIFUSE__CDP_POOL__URL = "APIFUSE__CDP_POOL__URL";
6
8
  export declare const DEFAULT_RESOLVER_TIMEOUT_MS = 180000;
9
+ /**
10
+ * SBSD is solved on the provider's own admitted session, so the transport profile must be
11
+ * declared up front. Other kinds, including `akamai_sensor`, keep the profile optional.
12
+ */
13
+ export declare function kindRequiresClientProfile(kind: ProviderChallengeKind): boolean;
@@ -1,6 +1,14 @@
1
1
  export const APIFUSE__RESOLVER__2CAPTCHA__API_KEY = "APIFUSE__RESOLVER__2CAPTCHA__API_KEY";
2
2
  export const APIFUSE__RESOLVER__CAPSOLVER__API_KEY = "APIFUSE__RESOLVER__CAPSOLVER__API_KEY";
3
3
  export const APIFUSE__RESOLVER__CAPMONSTER__API_KEY = "APIFUSE__RESOLVER__CAPMONSTER__API_KEY";
4
+ export const APIFUSE__RESOLVER__HYPERSOLUTIONS__API_KEY = "APIFUSE__RESOLVER__HYPERSOLUTIONS__API_KEY";
4
5
  export const APIFUSE__RESOLVER__TIMEOUT_MS = "APIFUSE__RESOLVER__TIMEOUT_MS";
5
6
  export const APIFUSE__CDP_POOL__URL = "APIFUSE__CDP_POOL__URL";
6
7
  export const DEFAULT_RESOLVER_TIMEOUT_MS = 180_000;
8
+ /**
9
+ * SBSD is solved on the provider's own admitted session, so the transport profile must be
10
+ * declared up front. Other kinds, including `akamai_sensor`, keep the profile optional.
11
+ */
12
+ export function kindRequiresClientProfile(kind) {
13
+ return kind === "akamai_sbsd";
14
+ }
@@ -1 +1,3 @@
1
- export { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, bindResolverSignal, createResolverClient, createResolverClientFromEnv, createUnsupportedResolverClient, DEFAULT_RESOLVER_VENDOR_PREFERENCE, DEFAULT_RESOLVER_TIMEOUT_MS, getResolverSolutionSource, invalidateCachedResolverSolution, invalidateResolverSolution, RESOLVER_ADAPTER_REGISTRY, RESOLVER_INSTRUMENTATION_METADATA, resolveProviderResolverVendors, type ResolverAdapterFactory, type ResolverInstrumentationMetadata, type ResolverRuntimeOptions, type ResolverSolutionSource, } from "./resolver.js";
1
+ export { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__HYPERSOLUTIONS__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, bindResolverSignal, createResolverClient, createResolverClientFromEnv, createUnsupportedResolverClient, DEFAULT_RESOLVER_VENDOR_PREFERENCE, DEFAULT_RESOLVER_TIMEOUT_MS, getResolverSolutionSource, invalidateCachedResolverSolution, invalidateResolverSolution, RESOLVER_ADAPTER_REGISTRY, RESOLVER_INSTRUMENTATION_METADATA, resolveProviderResolverVendors, type ResolverAdapterFactory, type ResolverInstrumentationMetadata, type ResolverRuntimeOptions, type ResolverSolutionSource, } from "./resolver.js";
2
+ export { type ResolverCacheReadTelemetryEvent, type ResolverCacheWriteTelemetryEvent, type ResolverFailoverTelemetryEvent, type ResolverIdentityTelemetryEvent, type ResolverOutcomeTelemetryEvent, type ResolverTelemetryAttemptOutcome, type ResolverTelemetryCacheStatus, type ResolverTelemetryCacheWriteReason, type ResolverTelemetryErrorClass, type ResolverTelemetryIdentitySource, type ResolverTelemetryOutcome, type ResolverTelemetryPhase, type ResolverTelemetrySink, type ResolverVendorAttemptTelemetryEvent, } from "./resolver-telemetry.js";
3
+ export type { ResolverPaidUsageContext, ResolverVendorUnavailableReason, } from "./resolver-vendors/types.js";
@@ -1 +1 @@
1
- export { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, bindResolverSignal, createResolverClient, createResolverClientFromEnv, createUnsupportedResolverClient, DEFAULT_RESOLVER_VENDOR_PREFERENCE, DEFAULT_RESOLVER_TIMEOUT_MS, getResolverSolutionSource, invalidateCachedResolverSolution, invalidateResolverSolution, RESOLVER_ADAPTER_REGISTRY, RESOLVER_INSTRUMENTATION_METADATA, resolveProviderResolverVendors, } from "./resolver.js";
1
+ export { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__HYPERSOLUTIONS__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, bindResolverSignal, createResolverClient, createResolverClientFromEnv, createUnsupportedResolverClient, DEFAULT_RESOLVER_VENDOR_PREFERENCE, DEFAULT_RESOLVER_TIMEOUT_MS, getResolverSolutionSource, invalidateCachedResolverSolution, invalidateResolverSolution, RESOLVER_ADAPTER_REGISTRY, RESOLVER_INSTRUMENTATION_METADATA, resolveProviderResolverVendors, } from "./resolver.js";
@@ -1,5 +1,13 @@
1
1
  import type { ChallengeSolution, ProviderChallenge, ResolverContext } from "../types.js";
2
2
  import type { TraceRecorder } from "./trace.js";
3
+ import type { ResolverTelemetrySink } from "./resolver-telemetry.js";
4
+ type ResolverTelemetryBinding = (sink: ResolverTelemetrySink) => ResolverContext;
5
+ /** Internal contract: reconstruct a chain with a new request sink, without mutating the host chain. */
6
+ export declare function registerResolverTelemetryBinding(resolver: ResolverContext, bind: ResolverTelemetryBinding): void;
7
+ /** Preserve reconstruction across SDK wrappers without capturing their current request sink. */
8
+ export declare function inheritResolverTelemetryBinding(resolver: ResolverContext, wrapper: ResolverContext): void;
9
+ /** SDK chains expose each vendor invocation; opaque host resolvers expose one custom invocation. */
10
+ export declare function bindResolverTelemetry(resolver: ResolverContext, sink: ResolverTelemetrySink | undefined): ResolverContext;
3
11
  export declare const RESOLVER_INSTRUMENTATION_METADATA: unique symbol;
4
12
  /**
5
13
  * Internal solve surface. The instrumentation layer threads its recorder as a
@@ -12,3 +20,4 @@ export type ResolverSolveWithRecorder = {
12
20
  solve(challenge: ProviderChallenge, signal?: AbortSignal, traceRecorder?: TraceRecorder): Promise<ChallengeSolution>;
13
21
  };
14
22
  export declare function createUnsupportedResolverClient(reason?: string): ResolverContext;
23
+ export {};