@apifuse/provider-sdk 2.2.0-beta.47 → 2.2.0-beta.49

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 (114) hide show
  1. package/AUTHORING.md +91 -36
  2. package/CHANGELOG.md +8 -0
  3. package/README.md +11 -9
  4. package/SUBMISSION.md +1 -1
  5. package/bin/apifuse-dev.ts +24 -13
  6. package/bin/apifuse-migrate-operation-declaration.ts +55 -0
  7. package/bin/apifuse-pack-smoke.ts +1 -1
  8. package/bin/apifuse-pack-types.ts +2 -1
  9. package/bin/apifuse-record.ts +30 -16
  10. package/bin/apifuse-submit-check.ts +20 -35
  11. package/dist/cli/commands.d.ts +1 -1
  12. package/dist/cli/commands.js +11 -0
  13. package/dist/cli/migrate-operation-declaration.d.ts +59 -0
  14. package/dist/cli/migrate-operation-declaration.js +1178 -0
  15. package/dist/cli/templates/provider/README.md.tpl +3 -3
  16. package/dist/cli/templates/provider/operations/ping.ts.tpl +2 -0
  17. package/dist/config/loader.d.ts +2 -0
  18. package/dist/config/loader.js +18 -7
  19. package/dist/contract-types.d.ts +11 -5
  20. package/dist/contract.js +21 -10
  21. package/dist/define.d.ts +25 -22
  22. package/dist/define.js +49 -75
  23. package/dist/dev.d.ts +3 -0
  24. package/dist/dev.js +1 -1
  25. package/dist/engine.d.ts +78 -0
  26. package/dist/engine.js +133 -0
  27. package/dist/index.d.ts +3 -2
  28. package/dist/index.js +2 -1
  29. package/dist/lint.d.ts +7 -15
  30. package/dist/lint.js +45 -70
  31. package/dist/provider.d.ts +3 -1
  32. package/dist/provider.js +1 -0
  33. package/dist/runtime/chrome149-header-order.d.ts +58 -0
  34. package/dist/runtime/chrome149-header-order.js +289 -0
  35. package/dist/runtime/env.js +12 -0
  36. package/dist/runtime/executor.d.ts +2 -1
  37. package/dist/runtime/executor.js +3 -36
  38. package/dist/runtime/insights.js +2 -2
  39. package/dist/runtime/otlp.d.ts +71 -2
  40. package/dist/runtime/otlp.js +397 -16
  41. package/dist/runtime/resolver-public.d.ts +1 -1
  42. package/dist/runtime/resolver-public.js +1 -1
  43. package/dist/runtime/resolver-vendors/capsolver.js +9 -3
  44. package/dist/runtime/resolver-vendors/twocaptcha.js +1 -0
  45. package/dist/runtime/resolver.d.ts +12 -0
  46. package/dist/runtime/resolver.js +45 -11
  47. package/dist/runtime/stealth.d.ts +13 -4
  48. package/dist/runtime/stealth.js +362 -85
  49. package/dist/runtime/trace-config.js +2 -1
  50. package/dist/runtime/trace.d.ts +5 -0
  51. package/dist/runtime/trace.js +43 -10
  52. package/dist/server/self-test.d.ts +1 -3
  53. package/dist/server/self-test.js +2 -12
  54. package/dist/server/serve-implementation.d.ts +6 -1
  55. package/dist/server/serve-implementation.js +55 -40
  56. package/dist/server/trace-output.d.ts +3 -1
  57. package/dist/server/trace-output.js +61 -2
  58. package/dist/stealth/profiles.d.ts +9 -8
  59. package/dist/stealth/profiles.js +123 -286
  60. package/dist/types.d.ts +116 -108
  61. package/package.json +2 -1
  62. package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +6 -0
  63. package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +3 -0
  64. package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +10 -0
  65. package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +8 -0
  66. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +5 -0
  67. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +16 -0
  68. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +3 -0
  69. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +31 -0
  70. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +11 -0
  71. package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +11 -0
  72. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +11 -0
  73. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +21 -0
  74. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +11 -0
  75. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +14 -0
  76. package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +7 -0
  77. package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +6 -0
  78. package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +7 -0
  79. package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +6 -0
  80. package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +7 -0
  81. package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +7 -0
  82. package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +15 -0
  83. package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +4 -0
  84. package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +12 -0
  85. package/src/cli/commands.ts +13 -0
  86. package/src/cli/migrate-operation-declaration.ts +1654 -0
  87. package/src/cli/templates/provider/README.md.tpl +3 -3
  88. package/src/cli/templates/provider/operations/ping.ts.tpl +2 -0
  89. package/src/config/loader.ts +31 -6
  90. package/src/contract-types.ts +11 -5
  91. package/src/contract.ts +21 -10
  92. package/src/define.ts +107 -119
  93. package/src/dev.ts +4 -1
  94. package/src/engine.ts +279 -0
  95. package/src/index.ts +13 -5
  96. package/src/lint.ts +58 -92
  97. package/src/provider.ts +25 -3
  98. package/src/runtime/chrome149-header-order.ts +330 -0
  99. package/src/runtime/env.ts +13 -0
  100. package/src/runtime/executor.ts +7 -40
  101. package/src/runtime/insights.ts +2 -2
  102. package/src/runtime/otlp.ts +467 -21
  103. package/src/runtime/resolver-public.ts +3 -0
  104. package/src/runtime/resolver-vendors/capsolver.ts +12 -4
  105. package/src/runtime/resolver-vendors/twocaptcha.ts +1 -0
  106. package/src/runtime/resolver.ts +68 -19
  107. package/src/runtime/stealth.ts +435 -103
  108. package/src/runtime/trace-config.ts +3 -2
  109. package/src/runtime/trace.ts +57 -17
  110. package/src/server/self-test.ts +2 -9
  111. package/src/server/serve-implementation.ts +89 -72
  112. package/src/server/trace-output.ts +99 -2
  113. package/src/stealth/profiles.ts +169 -327
  114. package/src/types.ts +114 -137
@@ -52,6 +52,17 @@ function insertCompletedSpan(completed, entry, maxSpans) {
52
52
  completed.splice(0, completed.length - maxSpans);
53
53
  }
54
54
  }
55
+ /** Hands the hook a copy whose attributes are detached from the recorded span; a hook that returns nothing fails closed. */
56
+ function prepareSpanForExport(span, sanitize) {
57
+ const copy = { ...span, attributes: { ...span.attributes } };
58
+ if (!sanitize)
59
+ return copy;
60
+ const sanitized = sanitize(copy);
61
+ if (!sanitized || typeof sanitized !== "object") {
62
+ throw new Error("sanitizeSpanForExport returned no span");
63
+ }
64
+ return sanitized;
65
+ }
55
66
  export function getTraceRecorder(trace) {
56
67
  return trace[TRACE_RECORDER] ?? null;
57
68
  }
@@ -60,14 +71,40 @@ export function createTraceContext(options = {}) {
60
71
  const completed = [];
61
72
  const activeSpanStorage = new AsyncLocalStorage();
62
73
  let sequence = 0;
63
- let traceContext;
74
+ // Export configuration (which can carry collector credentials) stays in this closure;
75
+ // the context object handed to provider code never exposes it.
76
+ const exportOptions = options.exportOptions;
77
+ const exportResourceAttributes = options.resourceAttributes
78
+ ? { ...options.resourceAttributes }
79
+ : undefined;
80
+ // One trace id per context so every export batch of this request shares it and
81
+ // two processes can never mint the same id.
82
+ const exportTraceId = crypto.randomUUID().replace(/-/g, "");
83
+ let exportScheduled = false;
84
+ // One pending batch per context: roots completing before the flush share it, and a span is
85
+ // handed to the exporter exactly once, so later roots never re-send earlier spans.
64
86
  const scheduleExport = () => {
65
- if (!traceContext._exportOptions) {
87
+ if (!exportOptions || exportScheduled) {
66
88
  return;
67
89
  }
68
- const spans = completed.map((entry) => ({ ...entry.span }));
90
+ exportScheduled = true;
69
91
  setImmediate(() => {
70
- void exportSpansOTLP(spans, traceContext._exportOptions, traceContext._resourceAttributes);
92
+ exportScheduled = false;
93
+ const pending = completed.filter((entry) => !entry.exported);
94
+ for (const entry of pending)
95
+ entry.exported = true;
96
+ if (pending.length === 0)
97
+ return;
98
+ // Sanitization runs off the request path; a faulty sanitizer drops the batch, never the request.
99
+ let spans;
100
+ try {
101
+ spans = pending.map((entry) => prepareSpanForExport(entry.span, options.sanitizeSpanForExport));
102
+ }
103
+ catch {
104
+ console.warn("[apifuse] OTLP export skipped; span sanitization failed.");
105
+ return;
106
+ }
107
+ void exportSpansOTLP(spans, exportOptions, exportResourceAttributes, exportTraceId);
71
108
  });
72
109
  };
73
110
  const recorder = {
@@ -101,7 +138,7 @@ export function createTraceContext(options = {}) {
101
138
  ...(error ? { error } : {}),
102
139
  ...(pendingSpan.parentId ? { parentId: pendingSpan.parentId } : {}),
103
140
  };
104
- insertCompletedSpan(completed, { sequence: pendingSpan.sequence, span }, maxSpans);
141
+ insertCompletedSpan(completed, { sequence: pendingSpan.sequence, span, exported: false }, maxSpans);
105
142
  options.onSpan?.(span);
106
143
  if (!pendingSpan.parentId) {
107
144
  scheduleExport();
@@ -123,17 +160,13 @@ export function createTraceContext(options = {}) {
123
160
  });
124
161
  },
125
162
  };
126
- traceContext = {
163
+ const traceContext = {
127
164
  span(name, fn) {
128
165
  return recorder.runSpan(name, fn);
129
166
  },
130
167
  getSpans() {
131
168
  return completed.map((entry) => ({ ...entry.span }));
132
169
  },
133
- ...(options.exportOptions ? { _exportOptions: options.exportOptions } : {}),
134
- ...(options.resourceAttributes
135
- ? { _resourceAttributes: { ...options.resourceAttributes } }
136
- : {}),
137
170
  [TRACE_RECORDER]: recorder,
138
171
  };
139
172
  return traceContext;
@@ -134,9 +134,7 @@ export interface SelfTestAppOptions {
134
134
  export declare function computeSelfTestPlanDigest(provider: ProviderDefinition): string;
135
135
  /**
136
136
  * Fail-closed read-only classification: an operation may self-test only when
137
- * its own metadata marks it read-only (annotations.readOnly or
138
- * toolRouter.riskClass "read") and nothing marks it side-effecting. Unclassified
139
- * operations are refused so the endpoint cannot become a mutation oracle.
137
+ * its authored safety source of truth classifies it as read-only.
140
138
  */
141
139
  export declare function isSelfTestReadOnlyOperation(operation: OperationDefinition): boolean;
142
140
  /** Binds the self-test executor to a tenant app's /v1 pipeline in-process. */
@@ -115,20 +115,10 @@ export function computeSelfTestPlanDigest(provider) {
115
115
  }
116
116
  /**
117
117
  * Fail-closed read-only classification: an operation may self-test only when
118
- * its own metadata marks it read-only (annotations.readOnly or
119
- * toolRouter.riskClass "read") and nothing marks it side-effecting. Unclassified
120
- * operations are refused so the endpoint cannot become a mutation oracle.
118
+ * its authored safety source of truth classifies it as read-only.
121
119
  */
122
120
  export function isSelfTestReadOnlyOperation(operation) {
123
- const annotations = operation.annotations;
124
- if (annotations?.readOnly === false)
125
- return false;
126
- if (annotations?.destructive === true)
127
- return false;
128
- const riskClass = operation.toolRouter?.riskClass;
129
- if (riskClass !== undefined && riskClass !== "read")
130
- return false;
131
- return annotations?.readOnly === true || riskClass === "read";
121
+ return operation.riskClass === "read";
132
122
  }
133
123
  /** Binds the self-test executor to a tenant app's /v1 pipeline in-process. */
134
124
  export function createSelfTestInvoke(app) {
@@ -1,9 +1,10 @@
1
1
  import { Hono } from "hono";
2
2
  import { z } from "zod";
3
+ import { type ProviderEngine } from "../engine.js";
3
4
  import { type ProviderErrorObservability } from "../errors.js";
4
5
  import { type ProviderErrorCategory } from "../observability.js";
5
6
  import { type ProxyTelemetryLogPayload } from "../runtime/proxy-telemetry.js";
6
- import type { OcrContext, ProviderContext, ProviderDefinition, ProviderRuntimeState, ResolverContext, SttContext } from "../types.js";
7
+ import type { OcrContext, ProviderContext, ProviderDefinition, ProviderFilesContext, ProviderRuntimeState, ResolverContext, SttContext } from "../types.js";
7
8
  import type { SelfTestCancellationLogEvent } from "./self-test.js";
8
9
  import { type AuthFlowRequest, type OperationRequest } from "./types.js";
9
10
  /** Compact SDK-owned error classification emitted separately from the public response body. */
@@ -140,6 +141,10 @@ export type ProviderServerLogEvent = (ProviderServerLogEventBase & {
140
141
  export type ProviderServerLogger = (event: ProviderServerLogEvent) => void;
141
142
  export type ProviderServerOptions<TContext extends Partial<ProviderContext> = ProviderContext> = {
142
143
  logger?: ProviderServerLogger;
144
+ /** Capability attachment boundary. Defaults to the local in-process engine. */
145
+ engine?: ProviderEngine;
146
+ /** Request-file resolver supplied by the engine host when `files` is declared. */
147
+ files?: ProviderFilesContext;
143
148
  /** Optional provider-specific operation executor. Stateful providers use this to preserve provider-local runtime semantics. */
144
149
  operationExecutor?: ProviderServerOperationExecutor<TContext>;
145
150
  /** Optional signed internal executor for stateful owner forwarding. */
@@ -7,6 +7,7 @@ import { z } from "zod";
7
7
  import { AuthAbortError, createAuthFlowHelpers } from "../auth.js";
8
8
  import { validateFailClosedDeclaration } from "../declaration-validation.js";
9
9
  import { safeProviderErrorObservability } from "../error-observability.js";
10
+ import { createInProcessProviderEngine, ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES, isEngineOwnedEnvName, readEngineProxyCredentials, } from "../engine.js";
10
11
  import { SDK_OWNED_PROVIDER_ERROR_CODES, SDK_RUNTIME_OWNED_ERROR_CODES, SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES, } from "../error-resolution.js";
11
12
  import { AuthError, isProviderError, isSessionExpiredError, isTransportError, isValidationError, ProviderError, } from "../errors.js";
12
13
  import { sanitizeDiagnosticText } from "../fixture-sanitization.js";
@@ -307,7 +308,7 @@ function getProviderStealthBaseUrl(provider) {
307
308
  return firstHost ? `https://${firstHost}` : undefined;
308
309
  }
309
310
  function getProviderStealthProfile(provider) {
310
- return provider.stealth?.profile ? getStealthProfile(provider.stealth.profile) : undefined;
311
+ return provider.stealth ? getStealthProfile(provider.stealth) : undefined;
311
312
  }
312
313
  function isProductionProviderBrowserMode(provider, env = process.env) {
313
314
  if (provider.runtime !== "browser") {
@@ -394,16 +395,21 @@ function resolveNativeProxyPolicy(provider) {
394
395
  return { mode: "disabled" };
395
396
  return undefined;
396
397
  }
398
+ function providerSecretNames(provider) {
399
+ return (provider.secrets?.map((secret) => secret.name) ?? []).filter((name) => !isEngineOwnedEnvName(name));
400
+ }
397
401
  function createProviderContext(provider, request, operationId, options, state = createUnsupportedProviderRuntimeState(), proxyTelemetry, signal) {
398
402
  const traceConfig = resolveTraceConfigFromEnv();
399
403
  const baseUrl = getProviderBaseUrl(provider);
400
404
  const stealthBaseUrl = getProviderStealthBaseUrl(provider);
401
405
  const stealthProfile = getProviderStealthProfile(provider);
402
406
  const proxyPolicy = resolveNativeProxyPolicy(provider);
407
+ const engineProxyCredentials = readEngineProxyCredentials();
403
408
  const proxyClientOptions = {
404
409
  upstream: { proxy: provider.proxy },
405
410
  affinityKey: resolveProviderProxyAffinityKey(provider, request, operationId),
406
411
  telemetry: proxyTelemetry,
412
+ engineCredentials: engineProxyCredentials,
407
413
  };
408
414
  const resolverIdentityScope = resolveProviderResolverIdentityScope(provider, proxyClientOptions.affinityKey, request.requestId);
409
415
  let wrappedContext;
@@ -411,12 +417,14 @@ function createProviderContext(provider, request, operationId, options, state =
411
417
  upstream: proxyClientOptions.upstream,
412
418
  affinityKey: proxyClientOptions.affinityKey,
413
419
  telemetry: proxyTelemetry,
420
+ engineCredentials: engineProxyCredentials,
414
421
  ...(signal ? { signal } : {}),
422
+ ...(provider.stealth ? { stealth: provider.stealth } : {}),
415
423
  };
416
424
  const { capabilityModules } = options;
417
425
  const logStealthCleanupError = (error) => logProviderCleanupError(options.logger, provider, "operation", operationId, request.requestId, "stealth", error);
418
426
  const env = createEnvContext([
419
- ...(provider.secrets?.map((secret) => secret.name) ?? []),
427
+ ...providerSecretNames(provider),
420
428
  PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
421
429
  ]);
422
430
  const credential = createCredentialContext({
@@ -431,9 +439,10 @@ function createProviderContext(provider, request, operationId, options, state =
431
439
  };
432
440
  const requestState = state.forConnection(requestContext.connectionId);
433
441
  const cache = createProviderCache({ providerId: provider.id });
434
- const context = wrapWithInstrumentation({
442
+ const bindings = {
435
443
  env,
436
444
  credential,
445
+ ...(options.files ? { files: options.files } : {}),
437
446
  request: requestContext,
438
447
  http: createHttpClient(baseUrl, {
439
448
  ...proxyClientOptions,
@@ -448,12 +457,8 @@ function createProviderContext(provider, request, operationId, options, state =
448
457
  state: requestState,
449
458
  stealth: stealthBaseUrl
450
459
  ? capabilityModules.stealth
451
- ? stealthProfile
452
- ? capabilityModules.stealth.createStealthClient(stealthBaseUrl, provider.stealth.profile, stealthClientOptions)
453
- : capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
454
- : stealthProfile
455
- ? createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, provider.stealth.profile, stealthClientOptions)
456
- : createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
460
+ ? capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
461
+ : createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
457
462
  : createStealthStub(),
458
463
  browser: provider.runtime === "browser"
459
464
  ? capabilityModules.browser.createBrowserClient({
@@ -472,7 +477,7 @@ function createProviderContext(provider, request, operationId, options, state =
472
477
  egress: provider.native.network,
473
478
  proxyPolicy: resolveNativeProxyPolicy(provider),
474
479
  affinityKey: proxyClientOptions.affinityKey,
475
- credentials: capabilityModules.nativeNetwork.createEnvVendorCredentialResolver(env),
480
+ credentials: capabilityModules.nativeNetwork.createEnvVendorCredentialResolver(createEnvContext([...ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES])),
476
481
  }),
477
482
  },
478
483
  }
@@ -517,7 +522,8 @@ function createProviderContext(provider, request, operationId, options, state =
517
522
  ...event,
518
523
  }),
519
524
  }),
520
- });
525
+ };
526
+ const context = wrapWithInstrumentation(options.engine.attach({ provider, bindings }));
521
527
  wrappedContext = context;
522
528
  return context;
523
529
  }
@@ -557,19 +563,23 @@ function createAuthFlowContext(provider, request, options, state, proxyTelemetry
557
563
  const stealthBaseUrl = getProviderStealthBaseUrl(provider);
558
564
  const stealthProfile = getProviderStealthProfile(provider);
559
565
  const proxyPolicy = resolveNativeProxyPolicy(provider);
566
+ const engineProxyCredentials = readEngineProxyCredentials();
560
567
  const contextData = request.context ?? {};
561
568
  const flowContextStore = createFlowContextStore(provider.context?.keys ?? Object.keys(contextData), contextData);
562
569
  const proxyClientOptions = {
563
570
  upstream: { proxy: provider.proxy },
564
571
  affinityKey: resolveAuthFlowProxyAffinityKey(provider, request),
565
572
  telemetry: proxyTelemetry,
573
+ engineCredentials: engineProxyCredentials,
566
574
  };
567
575
  const resolverIdentityScope = resolveProviderResolverIdentityScope(provider, proxyClientOptions.affinityKey, request.requestId);
568
576
  const stealthClientOptions = {
569
577
  upstream: proxyClientOptions.upstream,
570
578
  affinityKey: proxyClientOptions.affinityKey,
571
579
  telemetry: proxyTelemetry,
580
+ engineCredentials: engineProxyCredentials,
572
581
  ...(signal ? { signal } : {}),
582
+ ...(provider.stealth ? { stealth: provider.stealth } : {}),
573
583
  };
574
584
  const { capabilityModules } = options;
575
585
  const logStealthCleanupError = (error) => logProviderCleanupError(options.logger, provider, "auth", "flow", request.requestId, "stealth", error);
@@ -596,12 +606,8 @@ function createAuthFlowContext(provider, request, options, state, proxyTelemetry
596
606
  state: state.forConnection(resolveOperationConnectionId(request)),
597
607
  stealth: stealthBaseUrl
598
608
  ? capabilityModules.stealth
599
- ? stealthProfile
600
- ? capabilityModules.stealth.createStealthClient(stealthBaseUrl, provider.stealth.profile, stealthClientOptions)
601
- : capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
602
- : stealthProfile
603
- ? createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, provider.stealth.profile, stealthClientOptions)
604
- : createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
609
+ ? capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
610
+ : createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
605
611
  : createStealthStub(),
606
612
  ...(provider.native
607
613
  ? {
@@ -610,13 +616,13 @@ function createAuthFlowContext(provider, request, options, state, proxyTelemetry
610
616
  egress: provider.native.network,
611
617
  proxyPolicy: resolveNativeProxyPolicy(provider),
612
618
  affinityKey: proxyClientOptions.affinityKey,
613
- credentials: capabilityModules.nativeNetwork.createEnvVendorCredentialResolver(createEnvContext(provider.secrets?.map((secret) => secret.name))),
619
+ credentials: capabilityModules.nativeNetwork.createEnvVendorCredentialResolver(createEnvContext([...ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES])),
614
620
  }),
615
621
  },
616
622
  }
617
623
  : {}),
618
624
  env: createEnvContext([
619
- ...(provider.secrets?.map((secret) => secret.name) ?? []),
625
+ ...providerSecretNames(provider),
620
626
  ...(provider.auth?.mode === "oauth2_proxied" ? ["APIFUSE__AUTH_PROXY__URL"] : []),
621
627
  ]),
622
628
  credential,
@@ -766,11 +772,8 @@ function providerObservabilityDetails(error, declaredErrorCode) {
766
772
  const declaredRetryable = sdkOwnsErrorResolution(error)
767
773
  ? undefined
768
774
  : declaredErrorCode?.retryable;
769
- // Session-expiry surfaces the credential_expired category + the opt-in
770
- // retryable signal so Gateway/Credential Service can refresh and re-drive the
771
- // operation (see design.md §4.3 D3). Without this branch the auth error would
772
- // serialize as a bare 401 with no retryable/category, losing the refresh
773
- // signal for exactly the retryOnAuthRefresh operations it is meant to enable.
775
+ // Session-expiry surfaces the credential_expired category so Gateway and
776
+ // Credential Service can distinguish an expired credential from other 401s.
774
777
  if (isSessionExpiredError(error)) {
775
778
  return {
776
779
  category: providerErrorOption(error, "category") ?? "credential_expired",
@@ -962,7 +965,7 @@ function sdkOwnsErrorResolution(error) {
962
965
  }
963
966
  function buildOperationErrorCodeLookup(provider) {
964
967
  return new Map(Object.entries(provider.operations).flatMap(([operationId, operation]) => {
965
- const errorCodes = operation.docs?.errorCodes;
968
+ const errorCodes = operation.errorCodes;
966
969
  return errorCodes?.length
967
970
  ? [[operationId, new Map(errorCodes.map((entry) => [entry.code, entry]))]]
968
971
  : [];
@@ -1063,7 +1066,7 @@ function logProviderError(logger, provider, kind, route, requestId, error, statu
1063
1066
  ...(isUnregisteredProviderErrorCode
1064
1067
  ? {
1065
1068
  signal: "unregistered_provider_error_code",
1066
- signalFix: "Declare this code (with status and retryable) in the operation's docs.errorCodes so it serves its intended status instead of 500.",
1069
+ signalFix: "Declare this code (with status and retryable) in the operation's errorCodes so it serves its intended status instead of 500.",
1067
1070
  }
1068
1071
  : {}),
1069
1072
  ...(error instanceof z.ZodError ? { issues: zodDetails(error) } : {}),
@@ -1107,7 +1110,7 @@ function toJsonSuccessResponse(result, ctx) {
1107
1110
  if (result instanceof ReadableStream) {
1108
1111
  return new Response(result);
1109
1112
  }
1110
- const cacheMeta = ctx?.cache.responseMeta();
1113
+ const cacheMeta = ctx && "cache" in ctx ? ctx.cache.responseMeta() : undefined;
1111
1114
  const retryMeta = ctx ? retryResponseMeta.get(ctx) : undefined;
1112
1115
  const meta = cacheMeta || retryMeta
1113
1116
  ? {
@@ -1425,17 +1428,21 @@ async function handleOperation(provider, request, operationId, options, state =
1425
1428
  if (cleanupCalled)
1426
1429
  return;
1427
1430
  cleanupCalled = true;
1428
- try {
1429
- ctx.stealth.close?.();
1430
- }
1431
- catch (error) {
1432
- logProviderCleanupError(options.logger, provider, "operation", operationId, request.requestId, "stealth", error);
1433
- }
1434
- try {
1435
- await ctx.browser.close?.();
1431
+ if ("stealth" in ctx) {
1432
+ try {
1433
+ ctx.stealth.close?.();
1434
+ }
1435
+ catch (error) {
1436
+ logProviderCleanupError(options.logger, provider, "operation", operationId, request.requestId, "stealth", error);
1437
+ }
1436
1438
  }
1437
- catch (error) {
1438
- logProviderCleanupError(options.logger, provider, "operation", operationId, request.requestId, "browser", error);
1439
+ if ("browser" in ctx) {
1440
+ try {
1441
+ await ctx.browser.close?.();
1442
+ }
1443
+ catch (error) {
1444
+ logProviderCleanupError(options.logger, provider, "operation", operationId, request.requestId, "browser", error);
1445
+ }
1439
1446
  }
1440
1447
  };
1441
1448
  try {
@@ -1447,7 +1454,9 @@ async function handleOperation(provider, request, operationId, options, state =
1447
1454
  request,
1448
1455
  signal,
1449
1456
  })
1450
- : await executeOperation(provider, operationId, ctx, request.input);
1457
+ : await executeOperation(provider, operationId, ctx, request.input, {
1458
+ env: createEnvContext(providerSecretNames(provider)),
1459
+ });
1451
1460
  if (streaming && operation) {
1452
1461
  return toStreamingResponse(operation, result, cleanup, request.requestId);
1453
1462
  }
@@ -1670,7 +1679,11 @@ export function createServerApp(provider, options = {}) {
1670
1679
  return createServerAppWithCapabilityModules(runtimeProvider, runtimeOptions, loadProviderCapabilityModulesSync(runtimeProvider));
1671
1680
  }
1672
1681
  function createServerAppWithCapabilityModules(provider, serverOptions, capabilityModules) {
1673
- const options = { ...serverOptions, capabilityModules };
1682
+ const options = {
1683
+ ...serverOptions,
1684
+ capabilityModules,
1685
+ engine: serverOptions.engine ?? createInProcessProviderEngine(),
1686
+ };
1674
1687
  const app = new Hono();
1675
1688
  const logger = options.logger ?? defaultProviderServerLogger;
1676
1689
  const operationErrorCodes = buildOperationErrorCodeLookup(provider);
@@ -1686,7 +1699,7 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
1686
1699
  // instead of discovering it request-by-request. Deliberately log-only — a
1687
1700
  // boot crash would trade a structured MISSING_SECRET signal for
1688
1701
  // CrashLoopBackOff. Requests still fail closed via the executeOperation gate.
1689
- const missingSecretsAtBoot = listMissingRequiredSecrets(provider, createEnvContext(provider.secrets?.map((secret) => secret.name)));
1702
+ const missingSecretsAtBoot = listMissingRequiredSecrets(provider, createEnvContext(providerSecretNames(provider)));
1690
1703
  if (missingSecretsAtBoot.length > 0) {
1691
1704
  logger({
1692
1705
  level: "warn",
@@ -2064,6 +2077,8 @@ export async function serve(provider, options = {}) {
2064
2077
  const selfTestSecrets = resolveSelfTestMasterSecrets();
2065
2078
  const serverAppOptions = {
2066
2079
  logger: options.logger,
2080
+ engine: options.engine,
2081
+ files: options.files,
2067
2082
  ocr: options.ocr,
2068
2083
  stt: options.stt,
2069
2084
  resolver: options.resolver,
@@ -1,4 +1,6 @@
1
1
  import { type CreateTraceContextOptions } from "../runtime/trace.js";
2
2
  import type { TraceConfig } from "../types.js";
3
+ type EnvLike = Record<string, string | undefined>;
3
4
  /** Server-only trace output policy. Shared programmatic trace callers stay in-memory. */
4
- export declare function resolveServerTraceContextOptions(config: TraceConfig, resourceAttributes: Record<string, string>): CreateTraceContextOptions;
5
+ export declare function resolveServerTraceContextOptions(config: TraceConfig, resourceAttributes: Record<string, string>, env?: EnvLike): CreateTraceContextOptions;
6
+ export {};
@@ -1,7 +1,56 @@
1
+ import { isSensitiveFixtureKey, REDACTED_FIXTURE_VALUE } from "../fixture-sanitization.js";
2
+ import { OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, resolveOTLPExportOptions, resolveOTLPResourceAttributes, } from "../runtime/otlp.js";
1
3
  import { resolveTraceContextOptions, } from "../runtime/trace.js";
2
- import { sanitizeSpanForOutput } from "../trace-sanitization.js";
4
+ import { sanitizeSpanForOutput, sanitizeSpanNameForOutput, sanitizeTraceAttributes, } from "../trace-sanitization.js";
5
+ // Warn once per environment object: process.env in production, each injected env in tests.
6
+ const warnedEnvironments = new WeakSet();
7
+ const warnedResourceEnvironments = new WeakSet();
8
+ function warnDiscardedResourceAttributes(env, resolution) {
9
+ if (resolution.discarded.length === 0 || warnedResourceEnvironments.has(env))
10
+ return;
11
+ warnedResourceEnvironments.add(env);
12
+ console.warn(`[apifuse] ${resolution.discarded.join(", ")} could not be parsed as an OTel key=value list and was ignored.`);
13
+ }
14
+ function warnExportDisabled(env, resolution) {
15
+ if (warnedEnvironments.has(env))
16
+ return;
17
+ warnedEnvironments.add(env);
18
+ console.warn(resolution.status === "unconfigured"
19
+ ? `[apifuse] OTLP trace export is enabled but no endpoint is configured; set ${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT} or ${OTEL_EXPORTER_OTLP_ENDPOINT}. Trace export is disabled.`
20
+ : `[apifuse] OTLP trace export is enabled but ${resolution.source} ${resolution.reason}. Trace export is disabled.`);
21
+ }
22
+ function resolveServerOTLPExportOptions(config, env) {
23
+ const resolution = resolveOTLPExportOptions({
24
+ endpoint: config.otlp?.endpoint ?? config.endpoint,
25
+ headers: config.otlp?.headers,
26
+ timeout: config.otlp?.timeout,
27
+ }, env);
28
+ if (resolution.status === "resolved")
29
+ return resolution.options;
30
+ warnExportDisabled(env, resolution);
31
+ return undefined;
32
+ }
33
+ /**
34
+ * Per-request attributes carry client-supplied values (request_id) and get the full attribute
35
+ * sanitizer, exactly as on the console path. Operator-configured attributes from the process
36
+ * environment are identifiers rather than request data: their values are kept, with control
37
+ * characters neutralized, length bounded, and secret-named keys redacted.
38
+ */
39
+ function resolveExportResourceAttributes(requestAttributes, env) {
40
+ const resolution = resolveOTLPResourceAttributes({}, env);
41
+ warnDiscardedResourceAttributes(env, resolution);
42
+ const operatorAttributes = Object.fromEntries(Object.entries(resolution.attributes).map(([key, value]) => [
43
+ sanitizeSpanNameForOutput(key),
44
+ isSensitiveFixtureKey(key) ? REDACTED_FIXTURE_VALUE : sanitizeSpanNameForOutput(value),
45
+ ]));
46
+ const sanitizedRequestAttributes = Object.fromEntries(Object.entries(sanitizeTraceAttributes(requestAttributes)).map(([key, value]) => [
47
+ key,
48
+ String(value),
49
+ ]));
50
+ return { ...operatorAttributes, ...sanitizedRequestAttributes };
51
+ }
3
52
  /** Server-only trace output policy. Shared programmatic trace callers stay in-memory. */
4
- export function resolveServerTraceContextOptions(config, resourceAttributes) {
53
+ export function resolveServerTraceContextOptions(config, resourceAttributes, env = process.env) {
5
54
  const resolved = resolveTraceContextOptions(config);
6
55
  const outputEnabled = config.enabled !== false && config.exporter !== "none";
7
56
  const consoleHook = outputEnabled && (config.exporter === "console" || config.exporter === "json")
@@ -13,8 +62,18 @@ export function resolveServerTraceContextOptions(config, resourceAttributes) {
13
62
  resolved.onSpan?.(span);
14
63
  }
15
64
  : (consoleHook ?? resolved.onSpan);
65
+ const exportOptions = outputEnabled && config.exporter === "otlp"
66
+ ? resolveServerOTLPExportOptions(config, env)
67
+ : undefined;
16
68
  return {
17
69
  maxSpans: resolved.maxSpans,
18
70
  onSpan,
71
+ ...(exportOptions
72
+ ? {
73
+ exportOptions,
74
+ resourceAttributes: resolveExportResourceAttributes(resourceAttributes, env),
75
+ sanitizeSpanForExport: (span) => sanitizeSpanForOutput(span),
76
+ }
77
+ : {}),
19
78
  };
20
79
  }
@@ -1,8 +1,9 @@
1
- import type { StealthProfile } from "../types.js";
2
- export declare function generateLayer2Headers(profile: StealthProfile): Record<string, string>;
3
- export declare function getStealthProfile(name: string): StealthProfile;
4
- /** Returns the intent alias that replaces a registered version-pinned profile. */
5
- export declare function getStealthProfileIntentAlias(name: string): string | undefined;
6
- /** Internal compatibility catalog used by transport-parity tests. */
7
- export declare function listRegisteredStealthProfiles(): string[];
8
- export declare function listStealthProfiles(): string[];
1
+ import type { StealthProfile, StealthProfileDescriptor, StealthProfileSelection } from "../types.js";
2
+ export declare const DEFAULT_STEALTH_BROWSER: "chrome";
3
+ export declare const DEFAULT_STEALTH_OS: "macos";
4
+ /** Internal detector used to keep version-pinned string failures actionable. */
5
+ export declare function getVersionPinnedStealthReplacement(name: string): string | undefined;
6
+ /** Resolve omitted axes and validate that the resulting pair is supported. */
7
+ export declare function resolveStealthProfileSelection(selection: StealthProfileSelection | undefined, base?: StealthProfileDescriptor): StealthProfileDescriptor;
8
+ export declare function getStealthProfile(selection?: StealthProfileSelection): StealthProfile;
9
+ export declare function listStealthProfiles(): StealthProfileDescriptor[];