@apifuse/provider-sdk 2.2.0-beta.55 → 2.2.0-beta.57

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @apifuse/provider-sdk Changelog
2
2
 
3
+ ## 2.2.0-beta.57
4
+
5
+ - Release candidate for main commit 4087dafdeab771eb91691884ce7315b8c399f863.
6
+
7
+ ## 2.2.0-beta.56
8
+
9
+ - Release candidate for main commit 390167b3f51488be0beba25a1ea5eb425d3bf839.
10
+
3
11
  ## 2.2.0-beta.55
4
12
 
5
13
  - Release candidate for main commit 1c495af089940a4c666e6baf58dc0dd577771a40.
@@ -292,6 +292,115 @@ const NEGATIVE_CONTROLS = [
292
292
  "",
293
293
  ].join("\n"),
294
294
  },
295
+ {
296
+ filename: "negative-control-telemetry-header-vendor.ts",
297
+ expectedCode: "TS2322",
298
+ description: "gateway telemetry headers reject an unbranded vendor string",
299
+ source: [
300
+ 'import type { TelemetryContributor } from "@apifuse/provider-sdk";',
301
+ "",
302
+ "const bad: TelemetryContributor<{}, { vendor: string }> = {",
303
+ '\tkey: "resolver",',
304
+ "\ttoLogPayload: () => ({}),",
305
+ '\ttoHeaderPayload: () => ({ vendor: "free-text" }),',
306
+ "};",
307
+ "",
308
+ ].join("\n"),
309
+ },
310
+ {
311
+ filename: "negative-control-telemetry-header-host.ts",
312
+ expectedCode: "TS2322",
313
+ description: "gateway telemetry headers reject an unbranded host string",
314
+ source: [
315
+ 'import type { TelemetryContributor } from "@apifuse/provider-sdk";',
316
+ "",
317
+ "const bad: TelemetryContributor<{}, { host: string }> = {",
318
+ '\tkey: "http",',
319
+ "\ttoLogPayload: () => ({}),",
320
+ '\ttoHeaderPayload: () => ({ host: "x.example" }),',
321
+ "};",
322
+ "",
323
+ ].join("\n"),
324
+ },
325
+ {
326
+ filename: "negative-control-telemetry-tenant-vendor.ts",
327
+ expectedCode: "TS2322",
328
+ description: "tenant-neutral projections reject vendor identities",
329
+ source: [
330
+ 'import type { TenantNeutral } from "@apifuse/provider-sdk";',
331
+ "",
332
+ 'export const bad: TenantNeutral<{ vendorUsed: "smartproxy" }> = { vendorUsed: "smartproxy" };',
333
+ "",
334
+ ].join("\n"),
335
+ },
336
+ {
337
+ filename: "negative-control-telemetry-header-any.ts",
338
+ expectedCode: "TS2322",
339
+ description: "gateway telemetry projections reject any-valued properties",
340
+ source: [
341
+ 'import type { GatewayIngestible } from "@apifuse/provider-sdk";',
342
+ "",
343
+ 'export const bad: GatewayIngestible<{ x: any }> = { x: "free-text" };',
344
+ "",
345
+ ].join("\n"),
346
+ },
347
+ {
348
+ filename: "negative-control-telemetry-header-union-array.ts",
349
+ expectedCode: "TS2322",
350
+ description: "gateway telemetry projections reject arrays mixing numbers and strings",
351
+ source: [
352
+ 'import type { TelemetryContributor } from "@apifuse/provider-sdk";',
353
+ "",
354
+ "const bad: TelemetryContributor<{}, { values: (number | string)[] }> = {",
355
+ '\tkey: "resolver",',
356
+ "\ttoLogPayload: () => ({}),",
357
+ '\ttoHeaderPayload: () => ({ values: [1, "free prose"] }),',
358
+ "};",
359
+ "",
360
+ ].join("\n"),
361
+ },
362
+ {
363
+ filename: "negative-control-telemetry-opaque-identity.ts",
364
+ expectedCode: "TS2322",
365
+ description: "tenant opaque array brands are not accepted outside the cache keys property",
366
+ source: [
367
+ 'import type { TenantNeutral } from "@apifuse/provider-sdk";',
368
+ "",
369
+ "type LocalOpaque = string[] & { readonly __tenantOpaqueCacheKeys: true };",
370
+ "declare const opaque: LocalOpaque;",
371
+ "export const bad: TenantNeutral<{ identity: LocalOpaque }> = { identity: opaque };",
372
+ "",
373
+ ].join("\n"),
374
+ },
375
+ {
376
+ filename: "negative-control-telemetry-meta-vendor.ts",
377
+ expectedCode: "TS2322",
378
+ description: "success metadata rejects tenant-visible vendor identity keys",
379
+ source: [
380
+ 'import { closedEnum, type ClosedEnum, type TenantNeutral } from "@apifuse/provider-sdk";',
381
+ "",
382
+ 'type Meta = { cached: boolean; detail: { vendorUsed: ClosedEnum<"smartproxy"> } };',
383
+ 'export const bad: TenantNeutral<Meta> = { cached: false, detail: { vendorUsed: closedEnum("smartproxy") } };',
384
+ "",
385
+ ].join("\n"),
386
+ },
387
+ {
388
+ filename: "positive-control-proxy-telemetry-contributor.ts",
389
+ expectedCode: "",
390
+ description: "proxy telemetry contributor satisfies the public contributor contract",
391
+ source: [
392
+ 'import { closedEnum, type ClosedEnum, type GatewayIngestible, type ProxyTelemetryHeaderPayload, type ProxyTelemetryLogPayload, type TelemetryContributor } from "@apifuse/provider-sdk";',
393
+ "",
394
+ "export const proxy: TelemetryContributor<ProxyTelemetryLogPayload, ProxyTelemetryHeaderPayload> = {",
395
+ '\tkey: "proxy",',
396
+ '\ttoLogPayload: () => ({ kind: "unresolved", vendors: [] }),',
397
+ '\ttoHeaderPayload: () => ({ kind: closedEnum("unresolved"), vendors: [] }),',
398
+ "};",
399
+ 'type Values = { values: ClosedEnum<"a" | "b">[] };',
400
+ 'export const values: GatewayIngestible<Values> = { values: [closedEnum("a"), closedEnum("b")] };',
401
+ "",
402
+ ].join("\n"),
403
+ },
295
404
  ] as const;
296
405
 
297
406
  const tempRoot = mkdtempSync(join(tmpdir(), "apifuse-provider-sdk-pack-types-"));
@@ -513,6 +622,15 @@ function assertNegativeControlFails(consumerDir: string): void {
513
622
  ],
514
623
  { cwd: consumerDir, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] },
515
624
  );
625
+ if (negativeControl.expectedCode === "") {
626
+ if (result.status !== 0) {
627
+ throw new Error(
628
+ `Positive control "${negativeControl.description}" failed to compile:\n${result.stdout}\n${result.stderr}`,
629
+ );
630
+ }
631
+ console.log(`Positive control accepted: ${negativeControl.description}`);
632
+ continue;
633
+ }
516
634
  if (result.status === 0) {
517
635
  throw new Error(
518
636
  'Negative control "' +
@@ -528,6 +646,9 @@ function assertNegativeControlFails(consumerDir: string): void {
528
646
  `Negative control "${negativeControl.description}" (${negativeControl.filename}) failed for an unexpected reason (wanted ${negativeControl.expectedCode}):\n${output}`,
529
647
  );
530
648
  }
649
+ console.log(
650
+ `Negative control rejected (${negativeControl.expectedCode}): ${negativeControl.description}`,
651
+ );
531
652
  }
532
653
  }
533
654
 
@@ -45,6 +45,7 @@ export type OperationDeclarationRepositoryResult = {
45
45
  readonly providerRoot: string;
46
46
  readonly changedFiles: readonly string[];
47
47
  readonly operationCount: number;
48
+ readonly notes: readonly OperationDeclarationNote[];
48
49
  readonly sidecar?: string;
49
50
  readonly localeTodoCount: number;
50
51
  } | {
@@ -53,9 +54,15 @@ export type OperationDeclarationRepositoryResult = {
53
54
  readonly refusals: readonly OperationDeclarationRefusal[];
54
55
  /** Operations that were independently migratable before repository-atomic refusal. */
55
56
  readonly operationCount: number;
57
+ readonly notes: readonly OperationDeclarationNote[];
56
58
  readonly changedFiles: readonly string[];
57
59
  readonly localeTodoCount: number;
58
60
  };
61
+ export type OperationDeclarationNote = {
62
+ readonly code: "runtime_composed_registry";
63
+ readonly path: string;
64
+ readonly initializer: string;
65
+ };
59
66
  /** Run the file transform repository-wide, committing writes only if every file is provable. */
60
67
  export declare function migrateOperationDeclarationRepository(providerRootInput: string, options?: {
61
68
  readonly check?: boolean;
@@ -68,7 +68,8 @@ function migrateOperationDeclarationInternal(sourceText, fileName, options, cont
68
68
  }
69
69
  const constObjects = collectModuleConstObjects(source);
70
70
  const constArrays = collectModuleConstArrays(source);
71
- const discovery = discoverOperationSites(source, fileName, constObjects, options.operationIds, context.operationSites, context.excludedBindings);
71
+ const discovery = discoverOperationSites(source, fileName, constObjects, options.operationIds, context.operationSites, context.excludedBindings, context.runtimeComposedInitializers);
72
+ context.recordDiscoveredSites?.(discovery.discoveredCount);
72
73
  if (discovery.refusals.length > 0) {
73
74
  return { status: "refused", refusals: discovery.refusals };
74
75
  }
@@ -599,14 +600,15 @@ function replaceExampleLocaleMember(member, newName, localeKey, source) {
599
600
  text: `${newName}: ${JSON.stringify(localeKey)}`,
600
601
  };
601
602
  }
602
- function discoverOperationSites(source, fileName, constObjects, operationIds, operationSites, excludedBindings) {
603
+ function discoverOperationSites(source, fileName, constObjects, operationIds, operationSites, excludedBindings, runtimeComposedInitializers) {
603
604
  const sitesByStart = new Map();
605
+ const discoveredStarts = new Set();
604
606
  const localIds = new Map(operationIds ?? []);
605
607
  const localExcludedBindings = new Set(excludedBindings ?? []);
606
608
  const refusals = [];
607
609
  const operationFactories = collectSimpleOperationFactories(source);
608
610
  const factoryCallIds = new Map();
609
- for (const map of collectOperationsMaps(source, constObjects, fileName, refusals)) {
611
+ for (const map of collectOperationsMaps(source, constObjects, fileName, refusals, runtimeComposedInitializers)) {
610
612
  for (const property of map.properties) {
611
613
  if (ts.isSpreadAssignment(property))
612
614
  continue;
@@ -680,12 +682,15 @@ function discoverOperationSites(source, fileName, constObjects, operationIds, op
680
682
  if (ts.isCallExpression(node) && isOperationHelperCall(node)) {
681
683
  const argument = operationArgument(node);
682
684
  const bindingName = enclosingBindingName(node);
685
+ const unwrappedArgument = unwrapExpression(argument);
686
+ if (unwrappedArgument !== undefined && ts.isObjectLiteralExpression(unwrappedArgument)) {
687
+ discoveredStarts.add(unwrappedArgument.getStart(source));
688
+ }
683
689
  if (bindingName !== undefined && localExcludedBindings.has(bindingName))
684
690
  return;
685
691
  const operationKey = (bindingName === undefined ? undefined : localIds.get(bindingName)) ??
686
692
  bindingName ??
687
693
  "<anonymous>";
688
- const unwrappedArgument = unwrapExpression(argument);
689
694
  if (unwrappedArgument === undefined || !ts.isObjectLiteralExpression(unwrappedArgument)) {
690
695
  refusals.push(refusal(fileName, operationKey, "non_literal", "Operation helper argument must be an object literal."));
691
696
  }
@@ -711,8 +716,11 @@ function discoverOperationSites(source, fileName, constObjects, operationIds, op
711
716
  ts.forEachChild(node, visit);
712
717
  };
713
718
  visit(source);
719
+ for (const start of sitesByStart.keys())
720
+ discoveredStarts.add(start);
714
721
  return {
715
722
  sites: [...sitesByStart.values()].sort((left, right) => left.object.getStart(source) - right.object.getStart(source)),
723
+ discoveredCount: discoveredStarts.size,
716
724
  refusals,
717
725
  };
718
726
  }
@@ -741,7 +749,7 @@ function collectSimpleOperationFactories(source) {
741
749
  }
742
750
  return factories;
743
751
  }
744
- function collectOperationsMaps(source, constObjects, fileName, refusals) {
752
+ function collectOperationsMaps(source, constObjects, fileName, refusals, runtimeComposedInitializers) {
745
753
  const maps = new Map();
746
754
  const inspect = (expression, label) => {
747
755
  const unwrapped = unwrapExpression(expression);
@@ -758,6 +766,8 @@ function collectOperationsMaps(source, constObjects, fileName, refusals) {
758
766
  return;
759
767
  }
760
768
  if (ts.isCallExpression(unwrapped)) {
769
+ if (runtimeComposedInitializers?.has(expression.getStart(source)) === true)
770
+ return;
761
771
  refusals.push(refusal(fileName, label, "factory_composed_operations", "The operations map is produced by a factory call and cannot be statically enumerated."));
762
772
  }
763
773
  };
@@ -1274,6 +1284,7 @@ export function migrateOperationDeclarationRepository(providerRootInput, options
1274
1284
  const todos = [];
1275
1285
  const refusals = [...repositoryIndex.refusals];
1276
1286
  let operationCount = 0;
1287
+ let repositoryDiscoveredCount = 0;
1277
1288
  for (const sourcePath of sourceFiles) {
1278
1289
  const relativePath = slash(relative(providerRoot, sourcePath));
1279
1290
  const result = migrateOperationDeclarationInternal(readFileSync(sourcePath, "utf8"), relativePath, {
@@ -1283,6 +1294,10 @@ export function migrateOperationDeclarationRepository(providerRootInput, options
1283
1294
  operationSites: repositoryIndex.operationSites.get(sourcePath),
1284
1295
  excludedBindings: repositoryIndex.excludedBindings.get(sourcePath),
1285
1296
  staticObjectResolver: repositoryIndex.staticObjectResolverFor(sourcePath),
1297
+ runtimeComposedInitializers: repositoryIndex.runtimeComposedInitializers.get(sourcePath),
1298
+ recordDiscoveredSites: (count) => {
1299
+ repositoryDiscoveredCount += count;
1300
+ },
1286
1301
  });
1287
1302
  if (result.status === "refused") {
1288
1303
  refusals.push(...result.refusals);
@@ -1295,7 +1310,18 @@ export function migrateOperationDeclarationRepository(providerRootInput, options
1295
1310
  todos.push(...result.localeTodos);
1296
1311
  }
1297
1312
  }
1298
- if (repositoryIndex.declarations.length > 0 && repositoryIndex.discoveredCount === 0) {
1313
+ const notes = repositoryDiscoveredCount === 0
1314
+ ? []
1315
+ : repositoryIndex.declarations.flatMap((declaration) => declaration.runtimeInitializer === undefined
1316
+ ? []
1317
+ : [
1318
+ {
1319
+ code: "runtime_composed_registry",
1320
+ path: slash(relative(providerRoot, declaration.path)),
1321
+ initializer: declaration.runtimeInitializer,
1322
+ },
1323
+ ]);
1324
+ if (repositoryIndex.declarations.length > 0 && repositoryDiscoveredCount === 0) {
1299
1325
  for (const declaration of repositoryIndex.declarations) {
1300
1326
  refusals.push(refusal(slash(relative(providerRoot, declaration.path)), "<operations>", "no_operations_discovered", `Provider construct ${declaration.construct} declares operations via unresolved initializer ${JSON.stringify(declaration.initializer.getText(declaration.initializer.getSourceFile()))}, but repository-wide discovery found zero operation sites.`));
1301
1327
  }
@@ -1309,6 +1335,7 @@ export function migrateOperationDeclarationRepository(providerRootInput, options
1309
1335
  providerRoot,
1310
1336
  refusals,
1311
1337
  operationCount,
1338
+ notes,
1312
1339
  changedFiles,
1313
1340
  localeTodoCount: todos.length,
1314
1341
  };
@@ -1322,6 +1349,7 @@ export function migrateOperationDeclarationRepository(providerRootInput, options
1322
1349
  providerRoot,
1323
1350
  changedFiles: [],
1324
1351
  operationCount,
1352
+ notes,
1325
1353
  localeTodoCount: 0,
1326
1354
  };
1327
1355
  }
@@ -1338,6 +1366,7 @@ export function migrateOperationDeclarationRepository(providerRootInput, options
1338
1366
  providerRoot,
1339
1367
  changedFiles,
1340
1368
  operationCount,
1369
+ notes,
1341
1370
  sidecar,
1342
1371
  localeTodoCount: todos.length,
1343
1372
  };
@@ -1489,12 +1518,12 @@ function buildRepositoryOperationIndex(sourceFiles, providerRoot) {
1489
1518
  const operationIds = buildOperationIdIndex(sourceFiles);
1490
1519
  const operationSites = new Map();
1491
1520
  const excludedBindings = new Map();
1521
+ const runtimeComposedInitializers = new Map();
1492
1522
  const refusals = [];
1493
1523
  const declarations = [];
1494
1524
  const indexedProviderProperties = new Set();
1495
1525
  const factoryIds = new Map();
1496
1526
  const bindingCandidates = new Map();
1497
- let discoveredCount = 0;
1498
1527
  const relativePath = (path) => slash(relative(providerRoot, path));
1499
1528
  const resolutionRefusal = (path, operationKey, detail) => {
1500
1529
  refusals.push(refusal(relativePath(path), operationKey, "non_literal", detail));
@@ -1749,7 +1778,6 @@ function buildRepositoryOperationIndex(sourceFiles, providerRoot) {
1749
1778
  }
1750
1779
  sites.set(start, operationId);
1751
1780
  operationSites.set(path, sites);
1752
- discoveredCount += 1;
1753
1781
  };
1754
1782
  const classifyEntry = (entry) => {
1755
1783
  const resolved = resolveLocatedExpression(entry.value, new Set());
@@ -1793,7 +1821,6 @@ function buildRepositoryOperationIndex(sourceFiles, providerRoot) {
1793
1821
  };
1794
1822
  factory.ids.add(entry.operationId);
1795
1823
  factoryIds.set(key, factory);
1796
- discoveredCount += 1;
1797
1824
  return;
1798
1825
  }
1799
1826
  resolutionRefusal(resolved.value.path, entry.operationId, `Operation initializer ${JSON.stringify(expression.getText(resolved.value.source))} is not a raw object, operation helper, or simple same-file factory call.`);
@@ -1803,10 +1830,23 @@ function buildRepositoryOperationIndex(sourceFiles, providerRoot) {
1803
1830
  if (indexedProviderProperties.has(key))
1804
1831
  return;
1805
1832
  indexedProviderProperties.add(key);
1806
- declarations.push({ path, construct, initializer: node.initializer });
1833
+ const resolvedInitializer = resolveLocatedExpression({ path, source, expression: node.initializer }, new Set());
1834
+ let runtimeInitializer;
1835
+ if (resolvedInitializer.status === "resolved") {
1836
+ const resolvedExpression = unwrapExpression(resolvedInitializer.value.expression);
1837
+ if (resolvedExpression !== undefined && ts.isCallExpression(resolvedExpression)) {
1838
+ runtimeInitializer = resolvedExpression.getText(resolvedInitializer.value.source);
1839
+ const initializers = runtimeComposedInitializers.get(path) ?? new Set();
1840
+ initializers.add(node.initializer.getStart(source));
1841
+ runtimeComposedInitializers.set(path, initializers);
1842
+ }
1843
+ }
1844
+ declarations.push({ path, construct, initializer: node.initializer, runtimeInitializer });
1807
1845
  const flattened = flattenRegistry({ path, source, expression: node.initializer }, new Set());
1808
1846
  if ("detail" in flattened) {
1809
- resolutionRefusal(path, "<operations>", flattened.detail);
1847
+ if (runtimeInitializer === undefined) {
1848
+ resolutionRefusal(path, "<operations>", flattened.detail);
1849
+ }
1810
1850
  }
1811
1851
  else {
1812
1852
  for (const entry of flattened.entries.values())
@@ -1919,9 +1959,9 @@ function buildRepositoryOperationIndex(sourceFiles, providerRoot) {
1919
1959
  operationIds,
1920
1960
  operationSites,
1921
1961
  excludedBindings,
1962
+ runtimeComposedInitializers,
1922
1963
  refusals,
1923
1964
  declarations,
1924
- discoveredCount,
1925
1965
  staticObjectResolverFor: (currentPath) => (expression, source) => resolveStaticObject(expression, sources.has(source.fileName) ? source.fileName : currentPath),
1926
1966
  };
1927
1967
  }
package/dist/index.d.ts CHANGED
@@ -32,7 +32,8 @@ export { generateInsights } from "./runtime/insights.js";
32
32
  export { type InstrumentationOptions, type InstrumentedProviderContext, wrapWithInstrumentation, } from "./runtime/instrumentation.js";
33
33
  export type { PrevalidateResult } from "./runtime/prevalidate.js";
34
34
  export { getProviderBaseUrl } from "./runtime/provider.js";
35
- export type { ProxyTelemetryLogPayload, ProxyTelemetryResolvedPayload, ProxyTelemetryUnresolvedPayload, } from "./runtime/proxy-telemetry.js";
35
+ export type { ProxyTelemetryHeaderPayload, ProxyHash, ProxyTelemetryLogPayload, ProxyTelemetryResolvedPayload, ProxyTelemetryUnresolvedPayload, } from "./runtime/proxy-telemetry.js";
36
+ export { RequestTelemetry, closedEnum, type ClosedEnum, type GatewayIngestible, type RequestTelemetryLogPayload, type SpanIndex, type TelemetryContributor, type TelemetryKey, type TenantNeutral, } from "./runtime/request-telemetry.js";
36
37
  export { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, DEFAULT_RESOLVER_TIMEOUT_MS, } from "./runtime/resolver-config.js";
37
38
  export { createUnsupportedResolverClient } from "./runtime/resolver-shared.js";
38
39
  export type { ResolverRuntimeOptions } from "./runtime/resolver.js";
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@ export { NativeEgressGrantExpiredError, NativeEgressNotDeclaredError, NativeIdle
26
26
  export { generateInsights } from "./runtime/insights.js";
27
27
  export { wrapWithInstrumentation, } from "./runtime/instrumentation.js";
28
28
  export { getProviderBaseUrl } from "./runtime/provider.js";
29
+ export { RequestTelemetry, closedEnum, } from "./runtime/request-telemetry.js";
29
30
  export { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, DEFAULT_RESOLVER_TIMEOUT_MS, } from "./runtime/resolver-config.js";
30
31
  export { createUnsupportedResolverClient } from "./runtime/resolver-shared.js";
31
32
  export { assertRequiredSecretsPresent, listMissingRequiredSecrets, MISSING_SECRET_CODE, } from "./runtime/secrets.js";
@@ -1,5 +1,11 @@
1
1
  import type { ProxyAttemptTelemetryEvent, ProxyCacheStatus, ProxyProtocol, ProxyResolutionTelemetryEvent, ProxyTelemetrySink, ProxyUserAgentSource, ProxyVendorFailoverTelemetryEvent, ProxyVendorName, SmartproxyAllocatorBodyClass } from "../config/loader.js";
2
- export declare const PROVIDER_TELEMETRY_HEADER = "X-ApiFuse-Provider-Telemetry";
2
+ import { type ClosedEnum, type TelemetryContributor } from "./request-telemetry.js";
3
+ export { PROVIDER_TELEMETRY_HEADER } from "./request-telemetry.js";
4
+ declare const PROXY_HASH: unique symbol;
5
+ /** Bounded hexadecimal host hash used in proxy attempt samples. */
6
+ export type ProxyHash = ClosedEnum<string> & {
7
+ readonly [PROXY_HASH]: true;
8
+ };
3
9
  export type ProxyTelemetryResolvedPayload = {
4
10
  kind: "resolved";
5
11
  provider: ProxyVendorName;
@@ -77,11 +83,56 @@ export type ProxyTelemetryUnresolvedPayload = {
77
83
  }[];
78
84
  };
79
85
  export type ProxyTelemetryLogPayload = ProxyTelemetryResolvedPayload | ProxyTelemetryUnresolvedPayload;
80
- export declare class ProxyTelemetryCollector implements ProxyTelemetrySink {
86
+ /** Concrete gateway-safe projection of the unchanged proxy sibling. */
87
+ export type ProxyTelemetryHeaderPayload = {
88
+ kind: ClosedEnum<"resolved" | "unresolved">;
89
+ provider?: ClosedEnum<ProxyVendorName>;
90
+ userAgentSource?: ClosedEnum<ProxyUserAgentSource>;
91
+ protocol?: ClosedEnum<ProxyProtocol>;
92
+ cacheStatus?: ClosedEnum<ProxyCacheStatus>;
93
+ cacheHit?: boolean;
94
+ resolutionMs?: number;
95
+ allocatorMs?: number;
96
+ allocatorStatus?: number;
97
+ allocatorBodyClass?: ClosedEnum<SmartproxyAllocatorBodyClass>;
98
+ allocatorAttempts?: number;
99
+ lockWaitMs?: number;
100
+ redisReadMs?: number;
101
+ redisWriteMs?: number;
102
+ poolAgeMs?: number;
103
+ poolExpiresInMs?: number;
104
+ attempts?: number;
105
+ refreshes?: number;
106
+ attemptSamples?: {
107
+ n: number;
108
+ a: number;
109
+ i?: number;
110
+ h?: ProxyHash;
111
+ o: ClosedEnum<ProxyAttemptTelemetryEvent["outcome"]>;
112
+ c?: ClosedEnum<string>;
113
+ s?: number;
114
+ d?: number;
115
+ }[];
116
+ vendors?: ClosedEnum<ProxyVendorName>[];
117
+ failovers?: {
118
+ v: ClosedEnum<ProxyVendorName>;
119
+ nx?: ClosedEnum<ProxyVendorName>;
120
+ p: ClosedEnum<ProxyVendorFailoverTelemetryEvent["phase"]>;
121
+ r: ClosedEnum<ProxyVendorFailoverTelemetryEvent["reason"]>;
122
+ a?: number;
123
+ }[];
124
+ };
125
+ export declare class ProxyTelemetryCollector implements ProxyTelemetrySink, TelemetryContributor<ProxyTelemetryLogPayload, ProxyTelemetryHeaderPayload> {
81
126
  #private;
127
+ readonly key: "proxy";
82
128
  recordProxyResolution(event: ProxyResolutionTelemetryEvent): void;
129
+ /** Number of raw resolution events retained for bounded-history verification. */
130
+ get retainedResolutionEventCount(): number;
131
+ /** Total resolutions included in the incremental aggregate. */
132
+ get resolutionEventCount(): number;
83
133
  recordProxyVendorFailover(event: ProxyVendorFailoverTelemetryEvent): void;
84
134
  recordProxyAttempt(event: ProxyAttemptTelemetryEvent): void;
85
135
  toLogPayload(): ProxyTelemetryLogPayload | undefined;
136
+ toHeaderPayload(log: ProxyTelemetryLogPayload): ProxyTelemetryHeaderPayload;
86
137
  toHeaderValue(): string | undefined;
87
138
  }