@apifuse/provider-sdk 2.2.0-beta.38 → 2.2.0-beta.39

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.
@@ -21,7 +21,7 @@ import { wrapWithInstrumentation } from "../runtime/instrumentation.js";
21
21
  import { getProviderBaseUrl } from "../runtime/provider.js";
22
22
  import { createOcrClientFromEnv } from "../runtime/ocr.js";
23
23
  import { PROXY_AUTH_IP_DENIED_CODE, PROXY_EDGE_AUTH_REJECTED_CODE, PROXY_POOL_EXHAUSTED_CODE, } from "../runtime/proxy-errors.js";
24
- import { PROVIDER_TELEMETRY_HEADER, ProxyTelemetryCollector } from "../runtime/proxy-telemetry.js";
24
+ import { PROVIDER_TELEMETRY_HEADER, ProxyTelemetryCollector, } from "../runtime/proxy-telemetry.js";
25
25
  import { createUnsupportedResolverClient } from "../runtime/resolver-shared.js";
26
26
  import { assertRequiredSecretsPresent, listMissingRequiredSecrets, MISSING_SECRET_CODE, } from "../runtime/secrets.js";
27
27
  import { createProviderRuntimeStateFromEnv, createUnsupportedProviderRuntimeState, } from "../runtime/state.js";
@@ -348,7 +348,7 @@ function resolveOperationConnectionId(request) {
348
348
  // absent so it can never override a valid id or key a real scope. Requests
349
349
  // without any usable id fall back to the documented missing-connection
350
350
  // sentinel scope instead of scoping context/affinity/state under "".
351
- return normalizeConnectionId(request.connection?.id) ?? normalizeConnectionId(request.connectionId);
351
+ return (normalizeConnectionId(request.connection?.id) ?? normalizeConnectionId(request.connectionId));
352
352
  }
353
353
  function normalizeConnectionId(id) {
354
354
  return id === "" ? undefined : id;
@@ -512,7 +512,7 @@ export function resolveAuthFlowProxyAffinityKey(provider, request) {
512
512
  request.providerId ??
513
513
  provider.id);
514
514
  }
515
- function createAuthFlowContext(provider, request, options, state, signal) {
515
+ function createAuthFlowContext(provider, request, options, state, proxyTelemetry, signal) {
516
516
  const baseUrl = getProviderBaseUrl(provider);
517
517
  const stealthBaseUrl = getProviderStealthBaseUrl(provider);
518
518
  const stealthProfile = getProviderStealthProfile(provider);
@@ -522,11 +522,13 @@ function createAuthFlowContext(provider, request, options, state, signal) {
522
522
  const proxyClientOptions = {
523
523
  upstream: { proxy: provider.proxy },
524
524
  affinityKey: resolveAuthFlowProxyAffinityKey(provider, request),
525
+ telemetry: proxyTelemetry,
525
526
  };
526
527
  const resolverIdentityScope = resolveProviderResolverIdentityScope(provider, proxyClientOptions.affinityKey, request.requestId);
527
528
  const stealthClientOptions = {
528
529
  upstream: proxyClientOptions.upstream,
529
530
  affinityKey: proxyClientOptions.affinityKey,
531
+ telemetry: proxyTelemetry,
530
532
  };
531
533
  const { capabilityModules } = options;
532
534
  const logStealthCleanupError = (error) => logProviderCleanupError(options.logger, provider, "auth", "flow", request.requestId, "stealth", error);
@@ -947,7 +949,7 @@ function providerErrorCauseChain(error) {
947
949
  }
948
950
  return frames.length > 0 ? frames : undefined;
949
951
  }
950
- function logProviderError(logger, provider, kind, route, requestId, error, status, cost, declaredErrorCode) {
952
+ function logProviderError(logger, provider, kind, route, requestId, error, status, cost, declaredErrorCode, proxyTelemetry) {
951
953
  const code = isProviderError(error)
952
954
  ? (error.code ?? "provider_error")
953
955
  : error instanceof z.ZodError
@@ -965,6 +967,7 @@ function logProviderError(logger, provider, kind, route, requestId, error, statu
965
967
  typeof error.code === "string" &&
966
968
  !SDK_OWNED_PROVIDER_ERROR_CODES.has(error.code) &&
967
969
  declaredErrorCode === undefined;
970
+ const proxy = proxyTelemetry?.toLogPayload();
968
971
  const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
969
972
  emit({
970
973
  level: status >= 500 ? "error" : "warn",
@@ -975,6 +978,7 @@ function logProviderError(logger, provider, kind, route, requestId, error, statu
975
978
  ...(requestId ? { requestId } : {}),
976
979
  status,
977
980
  ...cost,
981
+ ...(proxy ? { proxy } : {}),
978
982
  code,
979
983
  errorClass,
980
984
  message,
@@ -1008,7 +1012,8 @@ function logProviderCleanupError(logger, provider, kind, operationId, requestId,
1008
1012
  message,
1009
1013
  });
1010
1014
  }
1011
- function logProviderSuccess(logger, provider, kind, route, requestId, status, cost) {
1015
+ function logProviderSuccess(logger, provider, kind, route, requestId, status, cost, proxyTelemetry) {
1016
+ const proxy = proxyTelemetry?.toLogPayload();
1012
1017
  const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
1013
1018
  emit({
1014
1019
  level: "info",
@@ -1019,6 +1024,7 @@ function logProviderSuccess(logger, provider, kind, route, requestId, status, co
1019
1024
  ...(requestId ? { requestId } : {}),
1020
1025
  status,
1021
1026
  ...cost,
1027
+ ...(proxy ? { proxy } : {}),
1022
1028
  });
1023
1029
  }
1024
1030
  function toJsonSuccessResponse(result, ctx) {
@@ -1395,7 +1401,7 @@ function responseWithProviderTelemetry(response, proxyTelemetry) {
1395
1401
  statusText: response.statusText,
1396
1402
  });
1397
1403
  }
1398
- async function handleAuthFlow(provider, request, route, options, state, signal) {
1404
+ async function handleAuthFlow(provider, request, route, options, state, proxyTelemetry, signal) {
1399
1405
  const flow = provider.auth?.flow;
1400
1406
  if (!flow) {
1401
1407
  throw new ProviderError("Auth flow is not configured", {
@@ -1407,7 +1413,7 @@ async function handleAuthFlow(provider, request, route, options, state, signal)
1407
1413
  // any flow code runs instead of at whatever point the ceremony first reads
1408
1414
  // the env. `abort` stays exempt: a user must always be able to cancel a
1409
1415
  // stranded flow even when provisioning is broken.
1410
- const { context, getPatch } = createAuthFlowContext(provider, request, options, state, signal);
1416
+ const { context, getPatch } = createAuthFlowContext(provider, request, options, state, proxyTelemetry, signal);
1411
1417
  try {
1412
1418
  if (route !== "abort") {
1413
1419
  assertRequiredSecretsPresent(provider, context.env);
@@ -1752,20 +1758,20 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
1752
1758
  body.headers = { ...requestHeaders, ...body.headers };
1753
1759
  const response = await handleOperation(provider, body, operation, options, state, proxyTelemetry, c.req.raw.signal);
1754
1760
  if (response instanceof Response) {
1755
- logProviderSuccess(logger, provider, "operation", operation, body.requestId, response.status, finishRequestCost(requestCost));
1761
+ logProviderSuccess(logger, provider, "operation", operation, body.requestId, response.status, finishRequestCost(requestCost), proxyTelemetry);
1756
1762
  return responseWithProviderTelemetry(response, proxyTelemetry);
1757
1763
  }
1758
1764
  const telemetryHeader = proxyTelemetry.toHeaderValue();
1759
1765
  if (telemetryHeader)
1760
1766
  c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1761
- logProviderSuccess(logger, provider, "operation", operation, body.requestId, 200, finishRequestCost(requestCost));
1767
+ logProviderSuccess(logger, provider, "operation", operation, body.requestId, 200, finishRequestCost(requestCost), proxyTelemetry);
1762
1768
  return c.json(response);
1763
1769
  }
1764
1770
  catch (error) {
1765
1771
  const declaredErrorCode = declaredErrorCodeFor(error, operation, operationErrorCodes);
1766
1772
  const status = toStatusCode(error, declaredErrorCode);
1767
1773
  const requestId = extractRequestId(rawBody);
1768
- logProviderError(logger, provider, "operation", operation, requestId, error, status, finishRequestCost(requestCost), declaredErrorCode);
1774
+ logProviderError(logger, provider, "operation", operation, requestId, error, status, finishRequestCost(requestCost), declaredErrorCode, proxyTelemetry);
1769
1775
  const telemetryHeader = proxyTelemetry.toHeaderValue();
1770
1776
  if (telemetryHeader)
1771
1777
  c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
@@ -1774,6 +1780,7 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
1774
1780
  });
1775
1781
  app.post("/auth/start", async (c) => {
1776
1782
  let rawBody;
1783
+ const proxyTelemetry = new ProxyTelemetryCollector();
1777
1784
  const requestCost = startRequestCost();
1778
1785
  try {
1779
1786
  rawBody = await c.req.raw
@@ -1781,19 +1788,28 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
1781
1788
  .json()
1782
1789
  .catch(() => undefined);
1783
1790
  const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1784
- const response = await handleAuthFlow(provider, body, "start", options, state, c.req.raw.signal);
1785
- logProviderSuccess(logger, provider, "auth", "start", body.requestId, response instanceof Response ? response.status : 200, finishRequestCost(requestCost));
1786
- return response instanceof Response ? response : c.json(response);
1791
+ const response = await handleAuthFlow(provider, body, "start", options, state, proxyTelemetry, c.req.raw.signal);
1792
+ logProviderSuccess(logger, provider, "auth", "start", body.requestId, response instanceof Response ? response.status : 200, finishRequestCost(requestCost), proxyTelemetry);
1793
+ if (response instanceof Response)
1794
+ return responseWithProviderTelemetry(response, proxyTelemetry);
1795
+ const telemetryHeader = proxyTelemetry.toHeaderValue();
1796
+ if (telemetryHeader)
1797
+ c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1798
+ return c.json(response);
1787
1799
  }
1788
1800
  catch (error) {
1789
1801
  const status = toStatusCode(error);
1790
1802
  const requestId = extractRequestId(rawBody);
1791
- logProviderError(logger, provider, "auth", "start", requestId, error, status, finishRequestCost(requestCost));
1803
+ logProviderError(logger, provider, "auth", "start", requestId, error, status, finishRequestCost(requestCost), undefined, proxyTelemetry);
1804
+ const telemetryHeader = proxyTelemetry.toHeaderValue();
1805
+ if (telemetryHeader)
1806
+ c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1792
1807
  return responseWithErrorObservability(c.json(toErrorResponse(error, requestId), status), error);
1793
1808
  }
1794
1809
  });
1795
1810
  app.post("/auth/continue", async (c) => {
1796
1811
  let rawBody;
1812
+ const proxyTelemetry = new ProxyTelemetryCollector();
1797
1813
  const requestCost = startRequestCost();
1798
1814
  try {
1799
1815
  rawBody = await c.req.raw
@@ -1801,19 +1817,28 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
1801
1817
  .json()
1802
1818
  .catch(() => undefined);
1803
1819
  const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1804
- const response = await handleAuthFlow(provider, body, "continue", options, state, c.req.raw.signal);
1805
- logProviderSuccess(logger, provider, "auth", "continue", body.requestId, response instanceof Response ? response.status : 200, finishRequestCost(requestCost));
1806
- return response instanceof Response ? response : c.json(response);
1820
+ const response = await handleAuthFlow(provider, body, "continue", options, state, proxyTelemetry, c.req.raw.signal);
1821
+ logProviderSuccess(logger, provider, "auth", "continue", body.requestId, response instanceof Response ? response.status : 200, finishRequestCost(requestCost), proxyTelemetry);
1822
+ if (response instanceof Response)
1823
+ return responseWithProviderTelemetry(response, proxyTelemetry);
1824
+ const telemetryHeader = proxyTelemetry.toHeaderValue();
1825
+ if (telemetryHeader)
1826
+ c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1827
+ return c.json(response);
1807
1828
  }
1808
1829
  catch (error) {
1809
1830
  const status = toStatusCode(error);
1810
1831
  const requestId = extractRequestId(rawBody);
1811
- logProviderError(logger, provider, "auth", "continue", requestId, error, status, finishRequestCost(requestCost));
1832
+ logProviderError(logger, provider, "auth", "continue", requestId, error, status, finishRequestCost(requestCost), undefined, proxyTelemetry);
1833
+ const telemetryHeader = proxyTelemetry.toHeaderValue();
1834
+ if (telemetryHeader)
1835
+ c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1812
1836
  return responseWithErrorObservability(c.json(toErrorResponse(error, requestId), status), error);
1813
1837
  }
1814
1838
  });
1815
1839
  app.post("/auth/poll", async (c) => {
1816
1840
  let rawBody;
1841
+ const proxyTelemetry = new ProxyTelemetryCollector();
1817
1842
  const requestCost = startRequestCost();
1818
1843
  try {
1819
1844
  rawBody = await c.req.raw
@@ -1821,19 +1846,28 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
1821
1846
  .json()
1822
1847
  .catch(() => undefined);
1823
1848
  const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1824
- const response = await handleAuthFlow(provider, body, "poll", options, state, c.req.raw.signal);
1825
- logProviderSuccess(logger, provider, "auth", "poll", body.requestId, response instanceof Response ? response.status : 200, finishRequestCost(requestCost));
1826
- return response instanceof Response ? response : c.json(response);
1849
+ const response = await handleAuthFlow(provider, body, "poll", options, state, proxyTelemetry, c.req.raw.signal);
1850
+ logProviderSuccess(logger, provider, "auth", "poll", body.requestId, response instanceof Response ? response.status : 200, finishRequestCost(requestCost), proxyTelemetry);
1851
+ if (response instanceof Response)
1852
+ return responseWithProviderTelemetry(response, proxyTelemetry);
1853
+ const telemetryHeader = proxyTelemetry.toHeaderValue();
1854
+ if (telemetryHeader)
1855
+ c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1856
+ return c.json(response);
1827
1857
  }
1828
1858
  catch (error) {
1829
1859
  const status = toStatusCode(error);
1830
1860
  const requestId = extractRequestId(rawBody);
1831
- logProviderError(logger, provider, "auth", "poll", requestId, error, status, finishRequestCost(requestCost));
1861
+ logProviderError(logger, provider, "auth", "poll", requestId, error, status, finishRequestCost(requestCost), undefined, proxyTelemetry);
1862
+ const telemetryHeader = proxyTelemetry.toHeaderValue();
1863
+ if (telemetryHeader)
1864
+ c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1832
1865
  return responseWithErrorObservability(c.json(toErrorResponse(error, requestId), status), error);
1833
1866
  }
1834
1867
  });
1835
1868
  app.post("/auth/refresh", async (c) => {
1836
1869
  let rawBody;
1870
+ const proxyTelemetry = new ProxyTelemetryCollector();
1837
1871
  const requestCost = startRequestCost();
1838
1872
  try {
1839
1873
  rawBody = await c.req.raw
@@ -1841,19 +1875,28 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
1841
1875
  .json()
1842
1876
  .catch(() => undefined);
1843
1877
  const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1844
- const response = await handleAuthFlow(provider, body, "refresh", options, state, c.req.raw.signal);
1845
- logProviderSuccess(logger, provider, "auth", "refresh", body.requestId, response instanceof Response ? response.status : 200, finishRequestCost(requestCost));
1846
- return response instanceof Response ? response : c.json(response);
1878
+ const response = await handleAuthFlow(provider, body, "refresh", options, state, proxyTelemetry, c.req.raw.signal);
1879
+ logProviderSuccess(logger, provider, "auth", "refresh", body.requestId, response instanceof Response ? response.status : 200, finishRequestCost(requestCost), proxyTelemetry);
1880
+ if (response instanceof Response)
1881
+ return responseWithProviderTelemetry(response, proxyTelemetry);
1882
+ const telemetryHeader = proxyTelemetry.toHeaderValue();
1883
+ if (telemetryHeader)
1884
+ c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1885
+ return c.json(response);
1847
1886
  }
1848
1887
  catch (error) {
1849
1888
  const status = toStatusCode(error);
1850
1889
  const requestId = extractRequestId(rawBody);
1851
- logProviderError(logger, provider, "auth", "refresh", requestId, error, status, finishRequestCost(requestCost));
1890
+ logProviderError(logger, provider, "auth", "refresh", requestId, error, status, finishRequestCost(requestCost), undefined, proxyTelemetry);
1891
+ const telemetryHeader = proxyTelemetry.toHeaderValue();
1892
+ if (telemetryHeader)
1893
+ c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1852
1894
  return responseWithErrorObservability(c.json(toErrorResponse(error, requestId), status), error);
1853
1895
  }
1854
1896
  });
1855
1897
  app.post("/auth/disconnect", async (c) => {
1856
1898
  let rawBody;
1899
+ const proxyTelemetry = new ProxyTelemetryCollector();
1857
1900
  const requestCost = startRequestCost();
1858
1901
  try {
1859
1902
  rawBody = await c.req.raw
@@ -1861,14 +1904,22 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
1861
1904
  .json()
1862
1905
  .catch(() => undefined);
1863
1906
  const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1864
- const response = await handleAuthFlow(provider, body, "abort", options, state, c.req.raw.signal);
1865
- logProviderSuccess(logger, provider, "auth", "disconnect", body.requestId, response instanceof Response ? response.status : 200, finishRequestCost(requestCost));
1866
- return response instanceof Response ? response : c.json(response);
1907
+ const response = await handleAuthFlow(provider, body, "abort", options, state, proxyTelemetry, c.req.raw.signal);
1908
+ logProviderSuccess(logger, provider, "auth", "disconnect", body.requestId, response instanceof Response ? response.status : 200, finishRequestCost(requestCost), proxyTelemetry);
1909
+ if (response instanceof Response)
1910
+ return responseWithProviderTelemetry(response, proxyTelemetry);
1911
+ const telemetryHeader = proxyTelemetry.toHeaderValue();
1912
+ if (telemetryHeader)
1913
+ c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1914
+ return c.json(response);
1867
1915
  }
1868
1916
  catch (error) {
1869
1917
  const status = toStatusCode(error);
1870
1918
  const requestId = extractRequestId(rawBody);
1871
- logProviderError(logger, provider, "auth", "disconnect", requestId, error, status, finishRequestCost(requestCost));
1919
+ logProviderError(logger, provider, "auth", "disconnect", requestId, error, status, finishRequestCost(requestCost), undefined, proxyTelemetry);
1920
+ const telemetryHeader = proxyTelemetry.toHeaderValue();
1921
+ if (telemetryHeader)
1922
+ c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1872
1923
  return responseWithErrorObservability(c.json(toErrorResponse(error, requestId), status), error);
1873
1924
  }
1874
1925
  });
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.2.0-beta.38",
2
+ "version": "2.2.0-beta.39",
3
3
  "name": "@apifuse/provider-sdk",
4
4
  "private": false,
5
5
  "type": "module",
@@ -59,8 +59,45 @@ export function validateFailClosedDeclaration(provider: ProviderDefinition): voi
59
59
  if (violations.length > 0) throw declarationInvalidError(violations);
60
60
  }
61
61
 
62
+ type ProviderDeclarationRulesInput = Pick<ProviderDefinition, "healthJourneys" | "proxy">;
63
+ type OperationDeclarationRulesInput = Pick<ProviderDefinition, "operations">;
64
+
65
+ /** Enforces fail-closed rules that only depend on the provider declaration. */
66
+ export function validateFailClosedProviderDeclaration(
67
+ provider: ProviderDeclarationRulesInput,
68
+ ): void {
69
+ const violations: DeclarationViolation[] = [];
70
+ collectProviderDeclarationViolations(provider, violations);
71
+ if (violations.length > 0) throw declarationInvalidError(violations);
72
+ }
73
+
74
+ /** Enforces fail-closed rules that depend on the operation implementation. */
75
+ export function validateFailClosedOperationDeclaration(
76
+ provider: OperationDeclarationRulesInput,
77
+ ): void {
78
+ const violations: DeclarationViolation[] = [];
79
+ collectOperationDeclarationViolations(provider, violations);
80
+ if (violations.length > 0) throw declarationInvalidError(violations);
81
+ }
82
+
83
+ function collectProviderDeclarationViolations(
84
+ provider: ProviderDeclarationRulesInput,
85
+ violations: DeclarationViolation[],
86
+ ): void {
87
+ validateHealthDeclaration(provider, violations);
88
+ validateProxyDeclaration(provider, violations);
89
+ }
90
+
91
+ function collectOperationDeclarationViolations(
92
+ provider: OperationDeclarationRulesInput,
93
+ violations: DeclarationViolation[],
94
+ ): void {
95
+ validateSchemaDeclaration(provider, violations);
96
+ validateOperationDeclaration(provider, violations);
97
+ }
98
+
62
99
  function validateHealthDeclaration(
63
- provider: ProviderDefinition,
100
+ provider: ProviderDeclarationRulesInput,
64
101
  violations: DeclarationViolation[],
65
102
  ): void {
66
103
  for (const [index, journey] of (provider.healthJourneys ?? []).entries()) {
@@ -113,7 +150,7 @@ function healthJourneyPath(journey: HealthJourneyDefinition, index: number): str
113
150
  }
114
151
 
115
152
  function validateSchemaDeclaration(
116
- provider: ProviderDefinition,
153
+ provider: OperationDeclarationRulesInput,
117
154
  violations: DeclarationViolation[],
118
155
  ): void {
119
156
  for (const [operationId, operation] of Object.entries(provider.operations ?? {})) {
@@ -153,7 +190,7 @@ const MANAGED_PROXY_VENDORS = new Set<ProviderProxyProvider>(["smartproxy", "nod
153
190
  const STATIC_PROXY_VENDORS = new Set<ProviderProxyProvider>(["custom", "decodo"]);
154
191
 
155
192
  function validateProxyDeclaration(
156
- provider: ProviderDefinition,
193
+ provider: ProviderDeclarationRulesInput,
157
194
  violations: DeclarationViolation[],
158
195
  ): void {
159
196
  if (provider.proxy === true) {
@@ -213,7 +250,7 @@ function declaredProxyVendors(policy: ProviderProxyPolicy): ProviderProxyProvide
213
250
  }
214
251
 
215
252
  function validateOperationDeclaration(
216
- provider: ProviderDefinition,
253
+ provider: OperationDeclarationRulesInput,
217
254
  violations: DeclarationViolation[],
218
255
  ): void {
219
256
  for (const [operationId, operation] of Object.entries(provider.operations ?? {})) {
package/src/define.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import ms from "ms";
2
2
 
3
- import { validateFailClosedDeclaration } from "./declaration-validation.js";
3
+ import {
4
+ validateFailClosedOperationDeclaration,
5
+ validateFailClosedProviderDeclaration,
6
+ } from "./declaration-validation.js";
4
7
  import { SDK_RUNTIME_OWNED_ERROR_CODES } from "./error-resolution.js";
5
8
  import { ProviderError, ValidationError } from "./errors.js";
6
9
  import {
@@ -30,12 +33,12 @@ import type {
30
33
  OperationWebSocketTransport,
31
34
  ProviderAccessConfig,
32
35
  ProviderChallengeKind,
33
- ProviderDefinition,
34
36
  ProviderContext,
35
37
  ProviderContextFor,
36
- ProviderOcrConfig,
38
+ ProviderDefinition,
37
39
  ProviderDeploymentOverrides,
38
40
  ProviderHealthMonitorConfig,
41
+ ProviderOcrConfig,
39
42
  ProviderProxyConfig,
40
43
  ProviderProxyProvider,
41
44
  ProviderPublicProfile,
@@ -806,13 +809,12 @@ function validateProxiedOAuthAuth(auth: Record<string, unknown>, providerId: str
806
809
  validateProxiedOAuthParams(config.tokenParams, "tokenParams", providerId);
807
810
  }
808
811
 
809
- function validateProviderShape(config: unknown): void {
812
+ function validateProviderDeclarationShape(config: unknown): void {
810
813
  assertObjectConfig(config);
811
814
  assertRequiredField(config, "id");
812
815
  assertRequiredField(config, "version", String(config.id));
813
816
  assertRequiredField(config, "runtime", String(config.id));
814
817
  assertRequiredField(config, "meta", String(config.id));
815
- assertRequiredField(config, "operations", String(config.id));
816
818
  if (typeof config.runtime === "string")
817
819
  assertLiteralField(config.runtime, "runtime", VALID_RUNTIMES, String(config.id));
818
820
  if (config.native !== undefined && config.runtime === "browser") {
@@ -910,6 +912,11 @@ function validateProviderShape(config: unknown): void {
910
912
  }
911
913
  }
912
914
 
915
+ function validateProviderImplementationShape(config: { id: string }): void {
916
+ const configRecord = config as unknown as Record<string, unknown>;
917
+ assertRequiredField(configRecord, "operations", String(config.id));
918
+ }
919
+
913
920
  function validateProviderProxy(config: {
914
921
  id: string;
915
922
  proxy?: ProviderProxyConfig;
@@ -2844,7 +2851,7 @@ export type ProviderBuilder<TDeclaration extends ProviderDeclaration> = <
2844
2851
  implementation: {
2845
2852
  operations: OperationMapConfig<TOperations, ProviderContextFor<TDeclaration>>;
2846
2853
  },
2847
- ) => ProviderDefinition & {
2854
+ ) => Omit<ProviderDefinition, "operations"> & {
2848
2855
  operations: OperationMapConfig<TOperations, ProviderContextFor<TDeclaration>>;
2849
2856
  };
2850
2857
 
@@ -2859,6 +2866,7 @@ export function defineProvider<const TDeclaration extends ProviderDeclaration>(
2859
2866
  Record<Exclude<keyof TDeclaration, keyof ProviderDeclaration>, never> &
2860
2867
  AuthStartNoInputGuard<TDeclaration>,
2861
2868
  ): ProviderBuilder<TDeclaration> {
2869
+ validateProviderDeclaration(declaration);
2862
2870
  const buildProvider = <TOperations extends Record<string, ProviderOperation>>(
2863
2871
  implementation: {
2864
2872
  operations: OperationMapConfig<TOperations, ProviderContextFor<TDeclaration>>;
@@ -2871,37 +2879,12 @@ export function defineProvider<const TDeclaration extends ProviderDeclaration>(
2871
2879
  return buildProvider as ProviderBuilder<TDeclaration>;
2872
2880
  }
2873
2881
 
2874
- function finalizeProvider<
2875
- TOperations extends Record<string, ProviderOperation>,
2876
- TContext,
2877
- >(
2878
- config: ProviderConfig<TOperations, TContext>,
2879
- ): ProviderDefinition & {
2880
- operations: OperationMapConfig<TOperations, TContext>;
2881
- } {
2882
- validateProviderShape(config);
2883
- const operations = resolveOperationFixtureRequests(config.operations);
2882
+ function validateProviderDeclaration(config: ProviderDeclaration): void {
2883
+ validateProviderDeclarationShape(config);
2884
2884
  if (!CONNECTOR_ID_REGEX.test(config.id))
2885
2885
  throw new ProviderError(`Invalid provider id: "${config.id}"`, {
2886
2886
  fix: 'Use lowercase alphanumeric with dashes, e.g., "korea-air-quality"',
2887
2887
  });
2888
- if (Object.keys(config.operations).length === 0)
2889
- throw new ProviderError(`Provider "${config.id}" must define at least one operation`, {
2890
- fix: "Add at least one operation to the operations object",
2891
- });
2892
- validateOperationIds(config.id, config.operations);
2893
- validateOperationAnnotations(config.id, config.operations);
2894
- validateOperationObservability(config.id, config.operations);
2895
- validateOperationErrorCodes(config.id, config.operations);
2896
- validateOperationTransports(config.id, config.operations);
2897
- validateOperationContracts(config.id, config.operations);
2898
- validateToolRouterMetadata(config.id, config.operations);
2899
- const journeyCoveredOperations = validateHealthJourneys(
2900
- config.id,
2901
- config.operations,
2902
- config.healthJourneys,
2903
- );
2904
- validateOperationHealthChecks(config.id, config.operations, journeyCoveredOperations);
2905
2888
  if (config.healthMonitor !== undefined && config.healthProbe !== undefined)
2906
2889
  throw new ValidationError(
2907
2890
  `Provider "${config.id}" declares both healthMonitor and healthProbe. They are aliases; declare exactly one.`,
@@ -2914,7 +2897,6 @@ function finalizeProvider<
2914
2897
  config.healthProbe ?? config.healthMonitor,
2915
2898
  config.healthProbe !== undefined ? "healthProbe" : "healthMonitor",
2916
2899
  );
2917
- validateOperationFixtures(config.id, operations);
2918
2900
  validateProviderDeployment(config.id, config.deployment);
2919
2901
  try {
2920
2902
  validateNativeProviderConfig(config.native);
@@ -2939,7 +2921,38 @@ function finalizeProvider<
2939
2921
  `Provider "${config.id}" cannot define browser config unless runtime is "browser"`,
2940
2922
  { fix: 'Set runtime: "browser" or remove the browser config' },
2941
2923
  );
2942
- const provider: ProviderDefinition & {
2924
+ validateFailClosedProviderDeclaration(config);
2925
+ }
2926
+
2927
+ function finalizeProvider<
2928
+ TOperations extends Record<string, ProviderOperation>,
2929
+ TContext,
2930
+ >(
2931
+ config: ProviderConfig<TOperations, TContext>,
2932
+ ): Omit<ProviderDefinition, "operations"> & {
2933
+ operations: OperationMapConfig<TOperations, TContext>;
2934
+ } {
2935
+ validateProviderImplementationShape(config);
2936
+ const operations = resolveOperationFixtureRequests(config.operations);
2937
+ if (Object.keys(config.operations).length === 0)
2938
+ throw new ProviderError(`Provider "${config.id}" must define at least one operation`, {
2939
+ fix: "Add at least one operation to the operations object",
2940
+ });
2941
+ validateOperationIds(config.id, config.operations);
2942
+ validateOperationAnnotations(config.id, config.operations);
2943
+ validateOperationObservability(config.id, config.operations);
2944
+ validateOperationErrorCodes(config.id, config.operations);
2945
+ validateOperationTransports(config.id, config.operations);
2946
+ validateOperationContracts(config.id, config.operations);
2947
+ validateToolRouterMetadata(config.id, config.operations);
2948
+ const journeyCoveredOperations = validateHealthJourneys(
2949
+ config.id,
2950
+ config.operations,
2951
+ config.healthJourneys,
2952
+ );
2953
+ validateOperationHealthChecks(config.id, config.operations, journeyCoveredOperations);
2954
+ validateOperationFixtures(config.id, operations);
2955
+ const provider: Omit<ProviderDefinition, "operations"> & {
2943
2956
  operations: OperationMapConfig<TOperations, TContext>;
2944
2957
  } = {
2945
2958
  id: config.id,
@@ -2963,14 +2976,15 @@ function finalizeProvider<
2963
2976
  credential: config.credential,
2964
2977
  context: config.context,
2965
2978
  meta: config.meta,
2966
- operations: operations as ProviderDefinition["operations"] &
2967
- OperationMapConfig<TOperations, TContext>,
2979
+ operations,
2968
2980
  // Transitional healthMonitor → healthProbe alias: mirror whichever field
2969
2981
  // was declared onto both so old and new consumers keep working.
2970
2982
  healthMonitor: config.healthMonitor ?? config.healthProbe,
2971
2983
  healthProbe: config.healthProbe ?? config.healthMonitor,
2972
2984
  healthJourneys: config.healthJourneys,
2973
2985
  };
2974
- validateFailClosedDeclaration(provider);
2986
+ // Declaration validation never invokes handlers, so their declaration-bound
2987
+ // context parameter is irrelevant to the runtime ProviderDefinition shape.
2988
+ validateFailClosedOperationDeclaration(provider as unknown as ProviderDefinition);
2975
2989
  return provider;
2976
2990
  }
package/src/index.ts CHANGED
@@ -6,12 +6,15 @@ export * from "./choice-token.js";
6
6
  export type {
7
7
  ApiFuseConfig,
8
8
  BrowserConfig,
9
+ ProxyCacheStatus,
9
10
  ProxyProtocol,
10
11
  ProxyResolutionOptions,
11
12
  ProxyResolutionSource,
13
+ ProxyUserAgentSource,
12
14
  ProxyVendorName,
13
15
  ResolvedProxyConfig,
14
16
  SessionConfig,
17
+ SmartproxyAllocatorBodyClass,
15
18
  } from "./config/loader.js";
16
19
  export { defineConfig, loadApiFuseConfig, resolveProxy } from "./config/loader.js";
17
20
  export {
@@ -174,6 +177,7 @@ export {
174
177
  } from "./runtime/instrumentation.js";
175
178
  export type { PrevalidateResult } from "./runtime/prevalidate.js";
176
179
  export { getProviderBaseUrl } from "./runtime/provider.js";
180
+ export type { ProxyTelemetryLogPayload } from "./runtime/proxy-telemetry.js";
177
181
  export {
178
182
  APIFUSE__CDP_POOL__URL,
179
183
  APIFUSE__RESOLVER__2CAPTCHA__API_KEY,
@@ -7,7 +7,7 @@ import {
7
7
  } from "../errors.js";
8
8
  import { z } from "zod";
9
9
  import { parseSchema } from "../schema.js";
10
- import type { ProviderContext, ProviderDefinition } from "../types.js";
10
+ import type { ProviderDefinition } from "../types.js";
11
11
  import { assertRequiredSecretsPresent } from "./secrets.js";
12
12
 
13
13
  export function isStreamingOperation(provider: ProviderDefinition, operationId: string): boolean {
@@ -26,10 +26,13 @@ export function isStreamingOperation(provider: ProviderDefinition, operationId:
26
26
  *
27
27
  * @see openspec/provider-sdk/03-sdk-core.md §3.6
28
28
  */
29
- export async function executeOperation(
30
- provider: ProviderDefinition,
31
- operationId: string,
32
- ctx: ProviderContext,
29
+ export async function executeOperation<
30
+ const TProvider extends ProviderDefinition,
31
+ const TOperationId extends keyof TProvider["operations"] & string,
32
+ >(
33
+ provider: TProvider,
34
+ operationId: TOperationId,
35
+ ctx: NoInfer<Parameters<TProvider["operations"][TOperationId]["handler"]>[0]>,
33
36
  input: unknown,
34
37
  _options?: { skipAuth?: boolean },
35
38
  ): Promise<unknown> {
@@ -47,7 +50,9 @@ export async function executeOperation(
47
50
  // handler, so every invocation path (serve /v1, self-test probes, perf,
48
51
  // record) fails with the same structured MISSING_SECRET error instead of a
49
52
  // handler-specific crash. Providers must not re-check presence locally.
50
- assertRequiredSecretsPresent(provider, ctx.env);
53
+ if (provider.secrets?.some((secret) => secret.required === true)) {
54
+ assertRequiredSecretsPresent(provider, "env" in ctx ? ctx.env : { get: () => undefined });
55
+ }
51
56
 
52
57
  const validatedInput = await parseSchema(
53
58
  operation.input,
@@ -27,7 +27,10 @@ import { RESOLVER_INSTRUMENTATION_METADATA } from "./resolver-shared.js";
27
27
 
28
28
  export interface InstrumentationOptions extends CreateTraceContextOptions {}
29
29
 
30
- export type InstrumentedProviderContext<T extends ProviderContext> = Omit<T, "trace"> & {
30
+ export type InstrumentedProviderContext<T extends Pick<ProviderContext, "trace">> = Omit<
31
+ T,
32
+ "trace"
33
+ > & {
31
34
  trace: TraceContext;
32
35
  };
33
36
 
@@ -850,7 +853,7 @@ function hasTraceOverrides(options: InstrumentationOptions): boolean {
850
853
  return options.maxSpans !== undefined || options.onSpan !== undefined;
851
854
  }
852
855
 
853
- export function wrapWithInstrumentation<T extends ProviderContext>(
856
+ export function wrapWithInstrumentation<T extends Pick<ProviderContext, "trace">>(
854
857
  ctx: T,
855
858
  options: InstrumentationOptions = {},
856
859
  ): InstrumentedProviderContext<T> {