@apifuse/provider-sdk 2.2.0-beta.1 → 2.2.0-beta.11

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 (261) hide show
  1. package/AUTHORING.md +106 -0
  2. package/CHANGELOG.md +41 -0
  3. package/README.md +5 -1
  4. package/SUBMISSION.md +1 -1
  5. package/bin/apifuse-check.ts +44 -59
  6. package/bin/apifuse-create.ts +1 -1
  7. package/bin/apifuse-dev.ts +27 -52
  8. package/bin/apifuse-pack-check.ts +14 -0
  9. package/bin/apifuse-pack-smoke.ts +22 -81
  10. package/bin/apifuse-pack-types.ts +266 -0
  11. package/bin/apifuse-perf.ts +45 -127
  12. package/bin/apifuse-record.ts +53 -70
  13. package/bin/apifuse-submit-check.ts +510 -45
  14. package/bin/apifuse-sync-assets.ts +117 -0
  15. package/bin/apifuse.ts +1 -1
  16. package/bin/submit-check-delimited-text.ts +50 -0
  17. package/bin/submit-check-xml-semantics.ts +204 -0
  18. package/bin/submit-check-xml.ts +134 -0
  19. package/dist/auth-turn/index.d.ts +3 -3
  20. package/dist/auth.d.ts +2 -2
  21. package/dist/auth.js +9 -18
  22. package/dist/ceremonies/index.d.ts +1 -1
  23. package/dist/ceremonies/index.js +8 -18
  24. package/dist/cli/commands.d.ts +1 -1
  25. package/dist/cli/commands.js +8 -0
  26. package/dist/cli/create.d.ts +3 -0
  27. package/dist/cli/create.js +34 -35
  28. package/dist/cli/prompt-assets.d.ts +80 -0
  29. package/dist/cli/prompt-assets.js +743 -0
  30. package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
  31. package/dist/config/loader.d.ts +149 -8
  32. package/dist/config/loader.js +378 -89
  33. package/dist/contract-serialization.d.ts +2 -2
  34. package/dist/contract-serialization.js +3 -6
  35. package/dist/contract-types.d.ts +2 -2
  36. package/dist/contract.d.ts +3 -3
  37. package/dist/contract.js +4 -6
  38. package/dist/define.d.ts +8 -1
  39. package/dist/define.js +119 -122
  40. package/dist/dev.d.ts +1 -1
  41. package/dist/dev.js +1 -1
  42. package/dist/errors.d.ts +4 -1
  43. package/dist/errors.js +48 -0
  44. package/dist/i18n/catalog.d.ts +2 -2
  45. package/dist/i18n/catalog.js +4 -10
  46. package/dist/i18n/index.d.ts +2 -2
  47. package/dist/i18n/index.js +2 -2
  48. package/dist/i18n/keys.d.ts +2 -2
  49. package/dist/index.d.ts +44 -42
  50. package/dist/index.js +39 -37
  51. package/dist/lint.js +8 -15
  52. package/dist/provider.d.ts +11 -11
  53. package/dist/provider.js +9 -9
  54. package/dist/public-schema-field-lint.d.ts +1 -1
  55. package/dist/recipes/gov-api.js +1 -1
  56. package/dist/runtime/auth-flow.d.ts +1 -1
  57. package/dist/runtime/auth-flow.js +3 -3
  58. package/dist/runtime/browser.d.ts +1 -1
  59. package/dist/runtime/browser.js +15 -29
  60. package/dist/runtime/cache.d.ts +1 -1
  61. package/dist/runtime/cache.js +4 -8
  62. package/dist/runtime/choice.d.ts +1 -1
  63. package/dist/runtime/choice.js +31 -35
  64. package/dist/runtime/credential.d.ts +1 -1
  65. package/dist/runtime/credential.js +1 -1
  66. package/dist/runtime/env.d.ts +1 -1
  67. package/dist/runtime/executor.d.ts +1 -1
  68. package/dist/runtime/executor.js +15 -3
  69. package/dist/runtime/http.d.ts +2 -2
  70. package/dist/runtime/http.js +144 -348
  71. package/dist/runtime/insights.d.ts +1 -1
  72. package/dist/runtime/insights.js +6 -13
  73. package/dist/runtime/instrumentation.d.ts +2 -2
  74. package/dist/runtime/instrumentation.js +56 -19
  75. package/dist/runtime/keyring.js +1 -1
  76. package/dist/runtime/namespace.js +1 -1
  77. package/dist/runtime/otlp.d.ts +1 -1
  78. package/dist/runtime/perf.d.ts +1 -1
  79. package/dist/runtime/provider.d.ts +1 -1
  80. package/dist/runtime/provider.js +1 -2
  81. package/dist/runtime/proxy-errors.d.ts +1 -1
  82. package/dist/runtime/proxy-errors.js +9 -7
  83. package/dist/runtime/proxy-nodemaven.d.ts +35 -0
  84. package/dist/runtime/proxy-nodemaven.js +128 -0
  85. package/dist/runtime/proxy-retry-policy.d.ts +40 -0
  86. package/dist/runtime/proxy-retry-policy.js +326 -0
  87. package/dist/runtime/proxy-telemetry.d.ts +2 -1
  88. package/dist/runtime/proxy-telemetry.js +55 -52
  89. package/dist/runtime/redis.d.ts +1 -1
  90. package/dist/runtime/redis.js +2 -4
  91. package/dist/runtime/request-options.d.ts +1 -1
  92. package/dist/runtime/secrets.d.ts +27 -0
  93. package/dist/runtime/secrets.js +51 -0
  94. package/dist/runtime/state.d.ts +2 -2
  95. package/dist/runtime/state.js +15 -4
  96. package/dist/runtime/stealth.d.ts +2 -2
  97. package/dist/runtime/stealth.js +108 -212
  98. package/dist/runtime/stt.d.ts +1 -1
  99. package/dist/runtime/stt.js +11 -15
  100. package/dist/runtime/trace.d.ts +2 -2
  101. package/dist/runtime/trace.js +2 -4
  102. package/dist/runtime/waterfall.d.ts +1 -1
  103. package/dist/schema.d.ts +1 -1
  104. package/dist/schema.js +7 -15
  105. package/dist/serve.d.ts +1 -1
  106. package/dist/serve.js +1 -1
  107. package/dist/server/index.d.ts +7 -7
  108. package/dist/server/index.js +6 -6
  109. package/dist/server/self-test-redaction.d.ts +1 -1
  110. package/dist/server/self-test-redaction.js +1 -1
  111. package/dist/server/self-test.d.ts +104 -3
  112. package/dist/server/self-test.js +673 -115
  113. package/dist/server/serve.d.ts +105 -4
  114. package/dist/server/serve.js +588 -80
  115. package/dist/server/types.d.ts +10 -9
  116. package/dist/server/types.js +3 -7
  117. package/dist/stateful/errors.d.ts +14 -0
  118. package/dist/stateful/errors.js +14 -0
  119. package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
  120. package/dist/stateful/http-provider-event-emitter.js +237 -0
  121. package/dist/stateful/http-session-owner-registry.d.ts +44 -0
  122. package/dist/stateful/http-session-owner-registry.js +210 -0
  123. package/dist/stateful/index.d.ts +18 -0
  124. package/dist/stateful/index.js +18 -0
  125. package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
  126. package/dist/stateful/provider-event-delivery-failures.js +43 -0
  127. package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
  128. package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
  129. package/dist/stateful/provider-event-pipeline.d.ts +50 -0
  130. package/dist/stateful/provider-event-pipeline.js +1 -0
  131. package/dist/stateful/provider-events.d.ts +101 -0
  132. package/dist/stateful/provider-events.js +289 -0
  133. package/dist/stateful/session-key.d.ts +15 -0
  134. package/dist/stateful/session-key.js +86 -0
  135. package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
  136. package/dist/stateful/stateful-provider-adapter-context.js +42 -0
  137. package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
  138. package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
  139. package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
  140. package/dist/stateful/stateful-provider-adapter.js +287 -0
  141. package/dist/stateful/stateful-provider-observability.d.ts +62 -0
  142. package/dist/stateful/stateful-provider-observability.js +161 -0
  143. package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
  144. package/dist/stateful/stateful-provider-owner-forwarder.js +207 -0
  145. package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
  146. package/dist/stateful/stateful-provider-runtime-context.js +60 -0
  147. package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
  148. package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
  149. package/dist/stateful/stateful-provider-session-routing.d.ts +71 -0
  150. package/dist/stateful/stateful-provider-session-routing.js +353 -0
  151. package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
  152. package/dist/stateful/stateful-provider-session-runtime.js +245 -0
  153. package/dist/stateful-signing.d.ts +18 -0
  154. package/dist/stateful-signing.js +27 -0
  155. package/dist/stealth/profiles.d.ts +1 -1
  156. package/dist/stealth/profiles.js +5 -14
  157. package/dist/stream.d.ts +1 -1
  158. package/dist/testing/index.d.ts +2 -2
  159. package/dist/testing/index.js +2 -2
  160. package/dist/testing/run.d.ts +1 -1
  161. package/dist/testing/run.js +7 -14
  162. package/dist/types.d.ts +87 -1
  163. package/dist/user-input.d.ts +30 -0
  164. package/dist/user-input.js +66 -0
  165. package/package.json +14 -5
  166. package/src/auth-turn/index.ts +1 -1
  167. package/src/auth.ts +28 -86
  168. package/src/ceremonies/index.ts +24 -78
  169. package/src/cli/commands.ts +10 -0
  170. package/src/cli/create.ts +42 -35
  171. package/src/cli/prompt-assets.ts +865 -0
  172. package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
  173. package/src/config/loader.ts +543 -208
  174. package/src/contract-serialization.ts +5 -11
  175. package/src/contract-types.ts +2 -2
  176. package/src/contract.ts +12 -28
  177. package/src/define.ts +247 -505
  178. package/src/dev.ts +4 -9
  179. package/src/errors.ts +58 -5
  180. package/src/i18n/catalog.ts +10 -32
  181. package/src/i18n/index.ts +2 -2
  182. package/src/i18n/keys.ts +5 -11
  183. package/src/index.ts +49 -42
  184. package/src/lint.ts +89 -159
  185. package/src/provider.ts +15 -11
  186. package/src/public-schema-field-lint.ts +7 -33
  187. package/src/recipes/gov-api.ts +2 -5
  188. package/src/runtime/auth-flow.ts +5 -7
  189. package/src/runtime/browser.ts +64 -187
  190. package/src/runtime/cache.ts +20 -67
  191. package/src/runtime/choice.ts +79 -132
  192. package/src/runtime/credential.ts +2 -2
  193. package/src/runtime/env.ts +1 -1
  194. package/src/runtime/executor.ts +23 -20
  195. package/src/runtime/http.ts +195 -559
  196. package/src/runtime/insights.ts +15 -53
  197. package/src/runtime/instrumentation.ts +78 -65
  198. package/src/runtime/keyring.ts +7 -19
  199. package/src/runtime/namespace.ts +2 -7
  200. package/src/runtime/otlp.ts +12 -23
  201. package/src/runtime/perf.ts +1 -1
  202. package/src/runtime/provider.ts +4 -9
  203. package/src/runtime/proxy-errors.ts +29 -42
  204. package/src/runtime/proxy-nodemaven.ts +178 -0
  205. package/src/runtime/proxy-retry-policy.ts +469 -0
  206. package/src/runtime/proxy-telemetry.ts +79 -77
  207. package/src/runtime/redis.ts +4 -12
  208. package/src/runtime/request-options.ts +4 -13
  209. package/src/runtime/secrets.ts +64 -0
  210. package/src/runtime/state.ts +41 -110
  211. package/src/runtime/stealth.ts +162 -377
  212. package/src/runtime/stt.ts +38 -94
  213. package/src/runtime/trace.ts +14 -44
  214. package/src/runtime/waterfall.ts +5 -18
  215. package/src/schema.ts +23 -84
  216. package/src/serve.ts +1 -1
  217. package/src/server/index.ts +24 -7
  218. package/src/server/self-test-redaction.ts +2 -2
  219. package/src/server/self-test.ts +857 -132
  220. package/src/server/serve.ts +859 -266
  221. package/src/server/types.ts +7 -19
  222. package/src/stateful/README.md +146 -0
  223. package/src/stateful/errors.ts +23 -0
  224. package/src/stateful/http-provider-event-emitter.ts +314 -0
  225. package/src/stateful/http-session-owner-registry.ts +306 -0
  226. package/src/stateful/index.ts +18 -0
  227. package/src/stateful/provider-event-delivery-failures.ts +80 -0
  228. package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
  229. package/src/stateful/provider-event-pipeline.ts +61 -0
  230. package/src/stateful/provider-events.ts +462 -0
  231. package/src/stateful/session-key.ts +111 -0
  232. package/src/stateful/stateful-provider-adapter-context.ts +59 -0
  233. package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
  234. package/src/stateful/stateful-provider-adapter.ts +562 -0
  235. package/src/stateful/stateful-provider-observability.ts +261 -0
  236. package/src/stateful/stateful-provider-owner-forwarder.ts +279 -0
  237. package/src/stateful/stateful-provider-runtime-context.ts +92 -0
  238. package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
  239. package/src/stateful/stateful-provider-session-routing.ts +555 -0
  240. package/src/stateful/stateful-provider-session-runtime.ts +403 -0
  241. package/src/stateful-signing.ts +46 -0
  242. package/src/stealth/profiles.ts +10 -26
  243. package/src/stream.ts +8 -19
  244. package/src/testing/index.ts +2 -2
  245. package/src/testing/run.ts +19 -67
  246. package/src/types.ts +88 -1
  247. package/src/user-input.ts +118 -0
  248. package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
  249. package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
  250. /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  251. /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  252. /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  253. /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  254. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  255. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
  256. /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  257. /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  258. /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  259. /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  260. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  261. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
@@ -1,17 +1,21 @@
1
1
  import type {
2
2
  ProxyAttemptTelemetryEvent,
3
3
  ProxyCacheStatus,
4
+ ProxyProtocol,
4
5
  ProxyResolutionTelemetryEvent,
5
6
  ProxyTelemetrySink,
7
+ ProxyVendorFailoverTelemetryEvent,
8
+ ProxyVendorName,
6
9
  SmartproxyAllocatorBodyClass,
7
- } from "../config/loader";
10
+ } from "../config/loader.js";
8
11
 
9
12
  export const PROVIDER_TELEMETRY_HEADER = "X-ApiFuse-Provider-Telemetry";
10
13
 
11
14
  type ProviderTelemetryHeader = {
12
15
  v: 1;
13
16
  proxy?: {
14
- provider: "smartproxy";
17
+ provider: ProxyVendorName;
18
+ protocol?: ProxyProtocol;
15
19
  cacheStatus: ProxyCacheStatus;
16
20
  cacheHit: boolean;
17
21
  resolutionMs: number;
@@ -27,6 +31,10 @@ type ProviderTelemetryHeader = {
27
31
  attempts: number;
28
32
  refreshes?: number;
29
33
  attemptSamples?: CompactProxyAttemptSample[];
34
+ /** Distinct vendors attempted across the resolution chain, in order seen. */
35
+ vendors?: ProxyVendorName[];
36
+ /** Cross-vendor failover events (bounded). */
37
+ failovers?: CompactVendorFailoverSample[];
30
38
  };
31
39
  };
32
40
 
@@ -41,8 +49,17 @@ type CompactProxyAttemptSample = {
41
49
  d?: number;
42
50
  };
43
51
 
52
+ type CompactVendorFailoverSample = {
53
+ v: ProxyVendorName;
54
+ nx?: ProxyVendorName;
55
+ p: "resolution" | "transport";
56
+ r: ProxyVendorFailoverTelemetryEvent["reason"];
57
+ a?: number;
58
+ };
59
+
44
60
  const MAX_HEADER_BYTES = 4_096;
45
61
  const MAX_PROXY_ATTEMPT_SAMPLES = 24;
62
+ const MAX_PROXY_FAILOVER_SAMPLES = 12;
46
63
 
47
64
  const CACHE_STATUS_SEVERITY: Record<ProxyCacheStatus, number> = {
48
65
  disabled: 0,
@@ -55,31 +72,18 @@ const CACHE_STATUS_SEVERITY: Record<ProxyCacheStatus, number> = {
55
72
  allocator: 7,
56
73
  };
57
74
 
58
- function sumOptional(
59
- left: number | undefined,
60
- right: number | undefined,
61
- ): number | undefined {
75
+ function sumOptional(left: number | undefined, right: number | undefined): number | undefined {
62
76
  const total = (left ?? 0) + (right ?? 0);
63
77
  return total > 0 ? total : undefined;
64
78
  }
65
79
 
66
- function maxOptional(
67
- left: number | undefined,
68
- right: number | undefined,
69
- ): number | undefined {
70
- const values = [left, right].filter(
71
- (value): value is number => typeof value === "number",
72
- );
80
+ function maxOptional(left: number | undefined, right: number | undefined): number | undefined {
81
+ const values = [left, right].filter((value): value is number => typeof value === "number");
73
82
  return values.length > 0 ? Math.max(...values) : undefined;
74
83
  }
75
84
 
76
- function worseStatus(
77
- left: ProxyCacheStatus,
78
- right: ProxyCacheStatus,
79
- ): ProxyCacheStatus {
80
- return CACHE_STATUS_SEVERITY[right] > CACHE_STATUS_SEVERITY[left]
81
- ? right
82
- : left;
85
+ function worseStatus(left: ProxyCacheStatus, right: ProxyCacheStatus): ProxyCacheStatus {
86
+ return CACHE_STATUS_SEVERITY[right] > CACHE_STATUS_SEVERITY[left] ? right : left;
83
87
  }
84
88
 
85
89
  function encodeBase64Url(value: string): string {
@@ -89,17 +93,17 @@ function encodeBase64Url(value: string): string {
89
93
  export class ProxyTelemetryCollector implements ProxyTelemetrySink {
90
94
  #events: ProxyResolutionTelemetryEvent[] = [];
91
95
  #attempts: ProxyAttemptTelemetryEvent[] = [];
96
+ #failovers: ProxyVendorFailoverTelemetryEvent[] = [];
92
97
 
93
98
  recordProxyResolution(event: ProxyResolutionTelemetryEvent): void {
94
99
  this.#events.push({
95
- provider: "smartproxy",
100
+ provider: event.provider,
101
+ ...(event.protocol ? { protocol: event.protocol } : {}),
96
102
  cacheStatus: event.cacheStatus,
97
103
  cacheHit: event.cacheHit,
98
104
  resolutionMs: Math.max(0, Math.floor(event.resolutionMs)),
99
105
  allocatorMs:
100
- event.allocatorMs === undefined
101
- ? undefined
102
- : Math.max(0, Math.floor(event.allocatorMs)),
106
+ event.allocatorMs === undefined ? undefined : Math.max(0, Math.floor(event.allocatorMs)),
103
107
  allocatorStatus:
104
108
  event.allocatorStatus === undefined
105
109
  ? undefined
@@ -110,37 +114,38 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
110
114
  ? undefined
111
115
  : Math.max(1, Math.floor(event.allocatorAttempts)),
112
116
  lockWaitMs:
113
- event.lockWaitMs === undefined
114
- ? undefined
115
- : Math.max(0, Math.floor(event.lockWaitMs)),
117
+ event.lockWaitMs === undefined ? undefined : Math.max(0, Math.floor(event.lockWaitMs)),
116
118
  redisReadMs:
117
- event.redisReadMs === undefined
118
- ? undefined
119
- : Math.max(0, Math.floor(event.redisReadMs)),
119
+ event.redisReadMs === undefined ? undefined : Math.max(0, Math.floor(event.redisReadMs)),
120
120
  redisWriteMs:
121
- event.redisWriteMs === undefined
122
- ? undefined
123
- : Math.max(0, Math.floor(event.redisWriteMs)),
121
+ event.redisWriteMs === undefined ? undefined : Math.max(0, Math.floor(event.redisWriteMs)),
124
122
  poolAgeMs:
125
- event.poolAgeMs === undefined
126
- ? undefined
127
- : Math.max(0, Math.floor(event.poolAgeMs)),
123
+ event.poolAgeMs === undefined ? undefined : Math.max(0, Math.floor(event.poolAgeMs)),
128
124
  poolExpiresInMs:
129
125
  event.poolExpiresInMs === undefined
130
126
  ? undefined
131
127
  : Math.max(0, Math.floor(event.poolExpiresInMs)),
132
128
  attempts: Math.max(1, Math.floor(event.attempts || 1)),
133
129
  refreshes:
134
- event.refreshes === undefined
135
- ? undefined
136
- : Math.max(0, Math.floor(event.refreshes)),
130
+ event.refreshes === undefined ? undefined : Math.max(0, Math.floor(event.refreshes)),
131
+ });
132
+ }
133
+
134
+ recordProxyVendorFailover(event: ProxyVendorFailoverTelemetryEvent): void {
135
+ if (this.#failovers.length >= MAX_PROXY_FAILOVER_SAMPLES) return;
136
+ this.#failovers.push({
137
+ vendor: event.vendor,
138
+ ...(event.nextVendor ? { nextVendor: event.nextVendor } : {}),
139
+ phase: event.phase,
140
+ reason: event.reason,
141
+ ...(event.attempt === undefined ? {} : { attempt: Math.max(0, Math.floor(event.attempt)) }),
137
142
  });
138
143
  }
139
144
 
140
145
  recordProxyAttempt(event: ProxyAttemptTelemetryEvent): void {
141
146
  if (this.#attempts.length >= MAX_PROXY_ATTEMPT_SAMPLES) return;
142
147
  this.#attempts.push({
143
- provider: "smartproxy",
148
+ provider: event.provider,
144
149
  attempt: Math.max(1, Math.floor(event.attempt || 1)),
145
150
  ...(event.poolIndex === undefined
146
151
  ? {}
@@ -148,9 +153,7 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
148
153
  ...(event.proxyHash ? { proxyHash: event.proxyHash.slice(0, 16) } : {}),
149
154
  outcome: event.outcome === "ok" ? "ok" : "error",
150
155
  ...(event.errorCode ? { errorCode: event.errorCode.slice(0, 80) } : {}),
151
- ...(event.status === undefined
152
- ? {}
153
- : { status: Math.max(0, Math.floor(event.status)) }),
156
+ ...(event.status === undefined ? {} : { status: Math.max(0, Math.floor(event.status)) }),
154
157
  ...(event.durationMs === undefined
155
158
  ? {}
156
159
  : { durationMs: Math.max(0, Math.floor(event.durationMs)) }),
@@ -161,27 +164,29 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
161
164
  const [first, ...rest] = this.#events;
162
165
  if (!first) return undefined;
163
166
 
167
+ // The serving vendor/protocol is the last recorded resolution (a failed
168
+ // vendor records first, the vendor that served records last).
169
+ const serving = this.#events[this.#events.length - 1] ?? first;
170
+ const vendors: ProxyVendorName[] = [];
171
+ for (const event of this.#events) {
172
+ if (!vendors.includes(event.provider)) vendors.push(event.provider);
173
+ }
174
+
164
175
  const aggregate = rest.reduce<ProxyResolutionTelemetryEvent>(
165
176
  (acc, event) => ({
166
- provider: "smartproxy",
177
+ provider: event.provider,
167
178
  cacheStatus: worseStatus(acc.cacheStatus, event.cacheStatus),
168
179
  cacheHit: acc.cacheHit && event.cacheHit,
169
180
  resolutionMs: acc.resolutionMs + event.resolutionMs,
170
181
  allocatorMs: sumOptional(acc.allocatorMs, event.allocatorMs),
171
182
  allocatorStatus: event.allocatorStatus ?? acc.allocatorStatus,
172
183
  allocatorBodyClass: event.allocatorBodyClass ?? acc.allocatorBodyClass,
173
- allocatorAttempts: sumOptional(
174
- acc.allocatorAttempts,
175
- event.allocatorAttempts,
176
- ),
184
+ allocatorAttempts: sumOptional(acc.allocatorAttempts, event.allocatorAttempts),
177
185
  lockWaitMs: sumOptional(acc.lockWaitMs, event.lockWaitMs),
178
186
  redisReadMs: sumOptional(acc.redisReadMs, event.redisReadMs),
179
187
  redisWriteMs: sumOptional(acc.redisWriteMs, event.redisWriteMs),
180
188
  poolAgeMs: maxOptional(acc.poolAgeMs, event.poolAgeMs),
181
- poolExpiresInMs: maxOptional(
182
- acc.poolExpiresInMs,
183
- event.poolExpiresInMs,
184
- ),
189
+ poolExpiresInMs: maxOptional(acc.poolExpiresInMs, event.poolExpiresInMs),
185
190
  attempts: acc.attempts + event.attempts,
186
191
  refreshes: sumOptional(acc.refreshes, event.refreshes),
187
192
  }),
@@ -190,13 +195,12 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
190
195
  const payload: ProviderTelemetryHeader = {
191
196
  v: 1,
192
197
  proxy: {
193
- provider: "smartproxy",
198
+ provider: serving.provider,
199
+ ...(serving.protocol ? { protocol: serving.protocol } : {}),
194
200
  cacheStatus: aggregate.cacheStatus,
195
201
  cacheHit: aggregate.cacheHit,
196
202
  resolutionMs: aggregate.resolutionMs,
197
- ...(aggregate.allocatorMs !== undefined
198
- ? { allocatorMs: aggregate.allocatorMs }
199
- : {}),
203
+ ...(aggregate.allocatorMs !== undefined ? { allocatorMs: aggregate.allocatorMs } : {}),
200
204
  ...(aggregate.allocatorStatus !== undefined
201
205
  ? { allocatorStatus: aggregate.allocatorStatus }
202
206
  : {}),
@@ -206,40 +210,38 @@ export class ProxyTelemetryCollector implements ProxyTelemetrySink {
206
210
  ...(aggregate.allocatorAttempts !== undefined
207
211
  ? { allocatorAttempts: aggregate.allocatorAttempts }
208
212
  : {}),
209
- ...(aggregate.lockWaitMs !== undefined
210
- ? { lockWaitMs: aggregate.lockWaitMs }
211
- : {}),
212
- ...(aggregate.redisReadMs !== undefined
213
- ? { redisReadMs: aggregate.redisReadMs }
214
- : {}),
215
- ...(aggregate.redisWriteMs !== undefined
216
- ? { redisWriteMs: aggregate.redisWriteMs }
217
- : {}),
218
- ...(aggregate.poolAgeMs !== undefined
219
- ? { poolAgeMs: aggregate.poolAgeMs }
220
- : {}),
213
+ ...(aggregate.lockWaitMs !== undefined ? { lockWaitMs: aggregate.lockWaitMs } : {}),
214
+ ...(aggregate.redisReadMs !== undefined ? { redisReadMs: aggregate.redisReadMs } : {}),
215
+ ...(aggregate.redisWriteMs !== undefined ? { redisWriteMs: aggregate.redisWriteMs } : {}),
216
+ ...(aggregate.poolAgeMs !== undefined ? { poolAgeMs: aggregate.poolAgeMs } : {}),
221
217
  ...(aggregate.poolExpiresInMs !== undefined
222
218
  ? { poolExpiresInMs: aggregate.poolExpiresInMs }
223
219
  : {}),
224
220
  attempts: aggregate.attempts,
225
- ...(aggregate.refreshes !== undefined
226
- ? { refreshes: aggregate.refreshes }
227
- : {}),
221
+ ...(aggregate.refreshes !== undefined ? { refreshes: aggregate.refreshes } : {}),
228
222
  ...(this.#attempts.length > 0
229
223
  ? {
230
224
  attemptSamples: this.#attempts.map((attempt, index) => ({
231
225
  n: index + 1,
232
226
  a: attempt.attempt,
233
- ...(attempt.poolIndex === undefined
234
- ? {}
235
- : { i: attempt.poolIndex }),
227
+ ...(attempt.poolIndex === undefined ? {} : { i: attempt.poolIndex }),
236
228
  ...(attempt.proxyHash ? { h: attempt.proxyHash } : {}),
237
229
  o: attempt.outcome,
238
230
  ...(attempt.errorCode ? { c: attempt.errorCode } : {}),
239
231
  ...(attempt.status === undefined ? {} : { s: attempt.status }),
240
- ...(attempt.durationMs === undefined
241
- ? {}
242
- : { d: attempt.durationMs }),
232
+ ...(attempt.durationMs === undefined ? {} : { d: attempt.durationMs }),
233
+ })),
234
+ }
235
+ : {}),
236
+ ...(vendors.length > 1 ? { vendors } : {}),
237
+ ...(this.#failovers.length > 0
238
+ ? {
239
+ failovers: this.#failovers.map((failover) => ({
240
+ v: failover.vendor,
241
+ ...(failover.nextVendor ? { nx: failover.nextVendor } : {}),
242
+ p: failover.phase,
243
+ r: failover.reason,
244
+ ...(failover.attempt === undefined ? {} : { a: failover.attempt }),
243
245
  })),
244
246
  }
245
247
  : {}),
@@ -1,4 +1,4 @@
1
- import Redis from "ioredis";
1
+ import { Redis } from "ioredis";
2
2
 
3
3
  export type ProviderRedisClient = Redis;
4
4
 
@@ -43,9 +43,7 @@ export async function withRedisTimeout<T>(
43
43
  }
44
44
  }, options.timeoutMs);
45
45
  });
46
- const operationResult = options.onError
47
- ? operation().catch(options.onError)
48
- : operation();
46
+ const operationResult = options.onError ? operation().catch(options.onError) : operation();
49
47
  return await Promise.race([operationResult, timeout]);
50
48
  } finally {
51
49
  if (timeoutId) clearTimeout(timeoutId);
@@ -56,10 +54,7 @@ export function redisStatus(redis: ProviderRedisClient): string {
56
54
  return redis.status;
57
55
  }
58
56
 
59
- async function waitForRedisReady(
60
- redis: ProviderRedisClient,
61
- timeoutMs: number,
62
- ): Promise<boolean> {
57
+ async function waitForRedisReady(redis: ProviderRedisClient, timeoutMs: number): Promise<boolean> {
63
58
  let timeoutId: ReturnType<typeof setTimeout> | undefined;
64
59
  let settled = false;
65
60
 
@@ -80,10 +75,7 @@ async function waitForRedisReady(
80
75
  const onReady = () => finish(true);
81
76
  const onUnavailable = () => finish(false);
82
77
 
83
- timeoutId = setTimeout(
84
- () => finish(redisStatus(redis) === "ready"),
85
- timeoutMs,
86
- );
78
+ timeoutId = setTimeout(() => finish(redisStatus(redis) === "ready"), timeoutMs);
87
79
  redis.once("ready", onReady);
88
80
  redis.once("close", onUnavailable);
89
81
  redis.once("end", onUnavailable);
@@ -1,12 +1,6 @@
1
- import type {
2
- RequestParamPrimitive,
3
- RequestParams,
4
- RequestParamValue,
5
- } from "../types";
1
+ import type { RequestParamPrimitive, RequestParams, RequestParamValue } from "../types.js";
6
2
 
7
- function isParamArray(
8
- value: RequestParamValue,
9
- ): value is readonly RequestParamPrimitive[] {
3
+ function isParamArray(value: RequestParamValue): value is readonly RequestParamPrimitive[] {
10
4
  return Array.isArray(value);
11
5
  }
12
6
 
@@ -29,8 +23,7 @@ export function appendQueryParams(url: string, params?: RequestParams): string {
29
23
  const parsed = new URL(url);
30
24
  for (const [key, value] of Object.entries(params)) {
31
25
  if (isParamArray(value)) {
32
- for (const item of value)
33
- appendQueryValue(parsed.searchParams, key, item);
26
+ for (const item of value) appendQueryValue(parsed.searchParams, key, item);
34
27
  continue;
35
28
  }
36
29
  appendQueryValue(parsed.searchParams, key, value);
@@ -39,9 +32,7 @@ export function appendQueryParams(url: string, params?: RequestParams): string {
39
32
  return parsed.toString();
40
33
  }
41
34
 
42
- export function normalizeHttpRequestBody(
43
- body: unknown,
44
- ): string | Buffer | undefined {
35
+ export function normalizeHttpRequestBody(body: unknown): string | Buffer | undefined {
45
36
  if (body === undefined) {
46
37
  return undefined;
47
38
  }
@@ -0,0 +1,64 @@
1
+ import { ProviderSecretError } from "../errors.js";
2
+ import type { EnvContext, ProviderDefinition } from "../types.js";
3
+
4
+ /**
5
+ * Canonical error code for a declared-but-unprovisioned provider secret.
6
+ *
7
+ * The SDK is the single source of truth for env/secret presence validation:
8
+ * providers declare secrets in `defineProvider({ secrets: [...] })` and the
9
+ * runtime enforces presence before any handler or auth-flow code runs.
10
+ * Provider-local presence guards (requireServiceKey/requireApiKey style) are a
11
+ * deprecated antipattern — see the `sdk-owned-secret-presence` submit-check
12
+ * rule.
13
+ */
14
+ export const MISSING_SECRET_CODE = "MISSING_SECRET";
15
+
16
+ /**
17
+ * Names of declared `required: true` secrets whose env values are unset or
18
+ * whitespace-only. Whitespace-only values count as missing for parity with the
19
+ * `.trim()` guards well-built providers used before the SDK owned this check —
20
+ * a blank value provisioned by a broken secret pipeline must not pass the gate.
21
+ */
22
+ export function listMissingRequiredSecrets(
23
+ provider: ProviderDefinition,
24
+ env: EnvContext,
25
+ ): string[] {
26
+ const missing: string[] = [];
27
+ for (const secret of provider.secrets ?? []) {
28
+ if (secret.required !== true) {
29
+ continue;
30
+ }
31
+ const value = env.get(secret.name);
32
+ if (value === undefined || value.trim() === "") {
33
+ missing.push(secret.name);
34
+ }
35
+ }
36
+ return missing;
37
+ }
38
+
39
+ /**
40
+ * Throws the canonical structured missing-secret error when any declared
41
+ * `required: true` secret is absent. All missing names are reported in a
42
+ * single error so operators can provision the full set in one pass instead of
43
+ * discovering them one deploy at a time (the 2026-07-22 unprovisioned-secret
44
+ * incident failure mode).
45
+ */
46
+ export function assertRequiredSecretsPresent(
47
+ provider: ProviderDefinition,
48
+ env: EnvContext,
49
+ ): void {
50
+ const missing = listMissingRequiredSecrets(provider, env);
51
+ if (missing.length === 0) {
52
+ return;
53
+ }
54
+ const names = missing.join(", ");
55
+ throw new ProviderSecretError(
56
+ `Missing required provider secret${missing.length > 1 ? "s" : ""}: ${names}`,
57
+ {
58
+ code: MISSING_SECRET_CODE,
59
+ category: "credential_unavailable",
60
+ retryable: false,
61
+ fix: `Provision ${names} in the provider environment (e.g. Doppler). Declared in defineProvider({ secrets: [...] }).`,
62
+ },
63
+ );
64
+ }