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

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 (43) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/cli/migrate-operation-declaration.d.ts +5 -1
  3. package/dist/cli/migrate-operation-declaration.js +645 -46
  4. package/dist/runtime/auth-flow.d.ts +1 -0
  5. package/dist/runtime/auth-flow.js +2 -0
  6. package/dist/runtime/trace.d.ts +7 -0
  7. package/dist/runtime/trace.js +24 -1
  8. package/dist/server/serve-implementation.d.ts +4 -0
  9. package/dist/server/serve-implementation.js +576 -321
  10. package/dist/types.d.ts +1 -0
  11. package/package.json +1 -1
  12. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-a.ts.txt +3 -0
  13. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-b.ts.txt +3 -0
  14. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-barrel.ts.txt +2 -0
  15. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-index.ts.txt +3 -0
  16. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-computed.ts.txt +6 -0
  17. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-a.ts.txt +1 -0
  18. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-b.ts.txt +1 -0
  19. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-index.ts.txt +3 -0
  20. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-barrel.ts.txt +7 -0
  21. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-catalog.ts.txt +19 -0
  22. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-docs.ts.txt +6 -0
  23. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-index.ts.txt +3 -0
  24. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-stores.ts.txt +8 -0
  25. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-indirect-config.ts.txt +5 -0
  26. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-nonstatic.ts.txt +4 -0
  27. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-package-index.ts.txt +3 -0
  28. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-index.ts.txt +3 -0
  29. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-leaf.ts.txt +10 -0
  30. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-one.ts.txt +1 -0
  31. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-two.ts.txt +1 -0
  32. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-barrel.ts.txt +2 -0
  33. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-index.ts.txt +3 -0
  34. package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-leaf.ts.txt +3 -0
  35. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-duplicate-provider.ts.txt +11 -0
  36. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-id-unresolved.ts.txt +7 -0
  37. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-many.ts.txt +18 -0
  38. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-one.ts.txt +13 -0
  39. package/src/cli/migrate-operation-declaration.ts +844 -49
  40. package/src/runtime/auth-flow.ts +3 -0
  41. package/src/runtime/trace.ts +37 -1
  42. package/src/server/serve-implementation.ts +759 -586
  43. package/src/types.ts +1 -0
@@ -1,4 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { AsyncLocalStorage } from "node:async_hooks";
2
3
  import { existsSync } from "node:fs";
3
4
  import { createRequire } from "node:module";
4
5
  import { join } from "node:path";
@@ -30,12 +31,13 @@ import { assertRequiredSecretsPresent, listMissingRequiredSecrets, MISSING_SECRE
30
31
  import { createProviderRuntimeStateFromEnv, createUnsupportedProviderRuntimeState, } from "../runtime/state.js";
31
32
  import { StealthCookieJar } from "../runtime/stealth-cookies.js";
32
33
  import { createSttClientFromEnv } from "../runtime/stt.js";
33
- import { createTraceContext } from "../runtime/trace.js";
34
+ import { createTraceContext, updateTraceContextExportMetadata, } from "../runtime/trace.js";
34
35
  import { resolveTraceConfigFromEnv } from "../runtime/trace-config.js";
35
36
  import { parseSchema } from "../schema.js";
36
37
  import { STATEFUL_NONCE_HEADER as STATEFUL_FORWARDING_NONCE_HEADER, STATEFUL_SIGNATURE_HEADER as STATEFUL_FORWARDING_SIGNATURE_HEADER, STATEFUL_TIMESTAMP_HEADER as STATEFUL_FORWARDING_TIMESTAMP_HEADER, verifyStatefulRequestSignature, } from "../stateful-signing.js";
37
38
  import { StatefulRoutingDeadlineError } from "../stateful/errors.js";
38
39
  import { getStealthProfile } from "../stealth/profiles.js";
40
+ import { sanitizeTraceAttributes } from "../trace-sanitization.js";
39
41
  import { APIFUSE_STREAM_DONE_EVENT, APIFUSE_STREAM_ERROR_EVENT, encodeSseEvent, error as streamError, } from "../stream.js";
40
42
  import { VALID_OPERATION_ERROR_STATUSES } from "../types.js";
41
43
  import { resolveSelfTestMasterSecrets } from "./self-test-token.js";
@@ -398,8 +400,7 @@ function resolveNativeProxyPolicy(provider) {
398
400
  function providerSecretNames(provider) {
399
401
  return (provider.secrets?.map((secret) => secret.name) ?? []).filter((name) => !isEngineOwnedEnvName(name));
400
402
  }
401
- function createProviderContext(provider, request, operationId, options, state = createUnsupportedProviderRuntimeState(), proxyTelemetry, signal) {
402
- const traceConfig = resolveTraceConfigFromEnv();
403
+ function createProviderContext(provider, request, operationId, options, state = createUnsupportedProviderRuntimeState(), scope, signal) {
403
404
  const baseUrl = getProviderBaseUrl(provider);
404
405
  const stealthBaseUrl = getProviderStealthBaseUrl(provider);
405
406
  const stealthProfile = getProviderStealthProfile(provider);
@@ -408,7 +409,7 @@ function createProviderContext(provider, request, operationId, options, state =
408
409
  const proxyClientOptions = {
409
410
  upstream: { proxy: provider.proxy },
410
411
  affinityKey: resolveProviderProxyAffinityKey(provider, request, operationId),
411
- telemetry: proxyTelemetry,
412
+ telemetry: scope.proxyTelemetry,
412
413
  engineCredentials: engineProxyCredentials,
413
414
  };
414
415
  const resolverIdentityScope = resolveProviderResolverIdentityScope(provider, proxyClientOptions.affinityKey, request.requestId);
@@ -416,7 +417,7 @@ function createProviderContext(provider, request, operationId, options, state =
416
417
  const stealthClientOptions = {
417
418
  upstream: proxyClientOptions.upstream,
418
419
  affinityKey: proxyClientOptions.affinityKey,
419
- telemetry: proxyTelemetry,
420
+ telemetry: scope.proxyTelemetry,
420
421
  engineCredentials: engineProxyCredentials,
421
422
  ...(signal ? { signal } : {}),
422
423
  ...(provider.stealth ? { stealth: provider.stealth } : {}),
@@ -482,13 +483,7 @@ function createProviderContext(provider, request, operationId, options, state =
482
483
  },
483
484
  }
484
485
  : {}),
485
- trace: traceConfig
486
- ? createTraceContext(resolveServerTraceContextOptions(traceConfig, {
487
- request_id: request.requestId,
488
- provider_id: provider.id,
489
- operation_id: operationId,
490
- }))
491
- : createTraceContext(),
486
+ trace: scope.trace,
492
487
  auth: createAuthStub(),
493
488
  ocr: options.ocr ?? createOcrClientFromEnv(provider.ocr),
494
489
  stt: options.stt ?? createSttClientFromEnv(provider.stt),
@@ -558,7 +553,7 @@ export function resolveAuthFlowProxyAffinityKey(provider, request) {
558
553
  request.providerId ??
559
554
  provider.id);
560
555
  }
561
- function createAuthFlowContext(provider, request, options, state, proxyTelemetry, signal) {
556
+ function createAuthFlowContext(provider, request, options, state, scope, signal) {
562
557
  const baseUrl = getProviderBaseUrl(provider);
563
558
  const stealthBaseUrl = getProviderStealthBaseUrl(provider);
564
559
  const stealthProfile = getProviderStealthProfile(provider);
@@ -569,14 +564,14 @@ function createAuthFlowContext(provider, request, options, state, proxyTelemetry
569
564
  const proxyClientOptions = {
570
565
  upstream: { proxy: provider.proxy },
571
566
  affinityKey: resolveAuthFlowProxyAffinityKey(provider, request),
572
- telemetry: proxyTelemetry,
567
+ telemetry: scope.proxyTelemetry,
573
568
  engineCredentials: engineProxyCredentials,
574
569
  };
575
570
  const resolverIdentityScope = resolveProviderResolverIdentityScope(provider, proxyClientOptions.affinityKey, request.requestId);
576
571
  const stealthClientOptions = {
577
572
  upstream: proxyClientOptions.upstream,
578
573
  affinityKey: proxyClientOptions.affinityKey,
579
- telemetry: proxyTelemetry,
574
+ telemetry: scope.proxyTelemetry,
580
575
  engineCredentials: engineProxyCredentials,
581
576
  ...(signal ? { signal } : {}),
582
577
  ...(provider.stealth ? { stealth: provider.stealth } : {}),
@@ -592,63 +587,65 @@ function createAuthFlowContext(provider, request, options, state, proxyTelemetry
592
587
  })
593
588
  : undefined;
594
589
  const cache = createProviderCache({ providerId: provider.id });
590
+ const context = wrapWithInstrumentation({
591
+ flowId: request.flowId,
592
+ connectionId: resolveOperationConnectionId(request),
593
+ externalRef: request.externalRef,
594
+ tenantId: request.tenantId ?? "",
595
+ providerId: request.providerId ?? provider.id,
596
+ trace: scope.trace,
597
+ http: createHttpClient(baseUrl, {
598
+ ...proxyClientOptions,
599
+ ...(signal ? { signal } : {}),
600
+ }),
601
+ state: state.forConnection(resolveOperationConnectionId(request)),
602
+ stealth: stealthBaseUrl
603
+ ? capabilityModules.stealth
604
+ ? capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
605
+ : createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
606
+ : createStealthStub(),
607
+ ...(provider.native
608
+ ? {
609
+ native: {
610
+ network: capabilityModules.nativeNetwork.createNativeNetworkClient({
611
+ egress: provider.native.network,
612
+ proxyPolicy: resolveNativeProxyPolicy(provider),
613
+ affinityKey: proxyClientOptions.affinityKey,
614
+ credentials: capabilityModules.nativeNetwork.createEnvVendorCredentialResolver(createEnvContext([...ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES])),
615
+ }),
616
+ },
617
+ }
618
+ : {}),
619
+ env: createEnvContext([
620
+ ...providerSecretNames(provider),
621
+ ...(provider.auth?.mode === "oauth2_proxied" ? ["APIFUSE__AUTH_PROXY__URL"] : []),
622
+ ]),
623
+ credential,
624
+ context: flowContextStore.context,
625
+ ocr: options.ocr ?? createOcrClientFromEnv(provider.ocr),
626
+ stt: options.stt ?? createSttClientFromEnv(provider.stt),
627
+ resolver: capabilityModules.resolver
628
+ ? capabilityModules.resolver.bindResolverSignal(options.resolver ??
629
+ capabilityModules.resolver.createResolverClientFromEnv(provider.resolver, undefined, {
630
+ allowedHosts: provider.allowedHosts,
631
+ cache,
632
+ identityScope: resolverIdentityScope,
633
+ ...(proxyPolicy
634
+ ? {
635
+ proxyIntent: {
636
+ mode: proxyPolicy.mode,
637
+ ...proxyClientOptions,
638
+ ...(stealthProfile ? { userAgent: stealthProfile.userAgent } : {}),
639
+ },
640
+ }
641
+ : {}),
642
+ }), signal)
643
+ : bindResolverSignalWithoutRuntime(options.resolver ??
644
+ createUnsupportedResolverClient("Provider does not declare resolver capability"), signal),
645
+ auth: createAuthFlowHelpers({ signal }),
646
+ });
595
647
  return {
596
- context: {
597
- flowId: request.flowId,
598
- connectionId: resolveOperationConnectionId(request),
599
- externalRef: request.externalRef,
600
- tenantId: request.tenantId ?? "",
601
- providerId: request.providerId ?? provider.id,
602
- http: createHttpClient(baseUrl, {
603
- ...proxyClientOptions,
604
- ...(signal ? { signal } : {}),
605
- }),
606
- state: state.forConnection(resolveOperationConnectionId(request)),
607
- stealth: stealthBaseUrl
608
- ? capabilityModules.stealth
609
- ? capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
610
- : createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
611
- : createStealthStub(),
612
- ...(provider.native
613
- ? {
614
- native: {
615
- network: capabilityModules.nativeNetwork.createNativeNetworkClient({
616
- egress: provider.native.network,
617
- proxyPolicy: resolveNativeProxyPolicy(provider),
618
- affinityKey: proxyClientOptions.affinityKey,
619
- credentials: capabilityModules.nativeNetwork.createEnvVendorCredentialResolver(createEnvContext([...ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES])),
620
- }),
621
- },
622
- }
623
- : {}),
624
- env: createEnvContext([
625
- ...providerSecretNames(provider),
626
- ...(provider.auth?.mode === "oauth2_proxied" ? ["APIFUSE__AUTH_PROXY__URL"] : []),
627
- ]),
628
- credential,
629
- context: flowContextStore.context,
630
- ocr: options.ocr ?? createOcrClientFromEnv(provider.ocr),
631
- stt: options.stt ?? createSttClientFromEnv(provider.stt),
632
- resolver: capabilityModules.resolver
633
- ? capabilityModules.resolver.bindResolverSignal(options.resolver ??
634
- capabilityModules.resolver.createResolverClientFromEnv(provider.resolver, undefined, {
635
- allowedHosts: provider.allowedHosts,
636
- cache,
637
- identityScope: resolverIdentityScope,
638
- ...(proxyPolicy
639
- ? {
640
- proxyIntent: {
641
- mode: proxyPolicy.mode,
642
- ...proxyClientOptions,
643
- ...(stealthProfile ? { userAgent: stealthProfile.userAgent } : {}),
644
- },
645
- }
646
- : {}),
647
- }), signal)
648
- : bindResolverSignalWithoutRuntime(options.resolver ??
649
- createUnsupportedResolverClient("Provider does not declare resolver capability"), signal),
650
- auth: createAuthFlowHelpers({ signal }),
651
- },
648
+ context,
652
649
  getPatch: flowContextStore.getPatch,
653
650
  };
654
651
  }
@@ -1017,7 +1014,7 @@ function providerErrorCauseChain(error) {
1017
1014
  }
1018
1015
  return frames.length > 0 ? frames : undefined;
1019
1016
  }
1020
- function logProviderError(logger, provider, kind, route, requestId, error, status, cost, declaredErrorCode, proxyTelemetry, observabilityDetails) {
1017
+ function logProviderError(logger, provider, kind, route, requestId, error, status, cost, declaredErrorCode, proxyTelemetry, observabilityDetails, correlation = {}) {
1021
1018
  const providerCode = isProviderError(error) ? providerErrorCode(error) : undefined;
1022
1019
  const code = isProviderError(error)
1023
1020
  ? (providerCode ?? "provider_error")
@@ -1051,6 +1048,14 @@ function logProviderError(logger, provider, kind, route, requestId, error, statu
1051
1048
  kind,
1052
1049
  route,
1053
1050
  ...(requestId ? { requestId } : {}),
1051
+ ...(correlation.connectionId !== undefined
1052
+ ? { connectionId: correlation.connectionId }
1053
+ : {}),
1054
+ ...(correlation.flowId !== undefined ? { flowId: correlation.flowId } : {}),
1055
+ ...(correlation.tenantId !== undefined ? { tenantId: correlation.tenantId } : {}),
1056
+ ...(correlation.requestedProviderId !== undefined
1057
+ ? { requestedProviderId: correlation.requestedProviderId }
1058
+ : {}),
1054
1059
  status,
1055
1060
  ...cost,
1056
1061
  ...(proxy ? { proxy } : {}),
@@ -1088,7 +1093,7 @@ function logProviderCleanupError(logger, provider, kind, operationId, requestId,
1088
1093
  message,
1089
1094
  });
1090
1095
  }
1091
- function logProviderSuccess(logger, provider, kind, route, requestId, status, cost, proxyTelemetry) {
1096
+ function logProviderSuccess(logger, provider, kind, route, requestId, status, cost, proxyTelemetry, correlation = {}) {
1092
1097
  const proxy = proxyTelemetry?.toLogPayload();
1093
1098
  const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
1094
1099
  emit({
@@ -1098,11 +1103,301 @@ function logProviderSuccess(logger, provider, kind, route, requestId, status, co
1098
1103
  kind,
1099
1104
  route,
1100
1105
  ...(requestId ? { requestId } : {}),
1106
+ ...(correlation.connectionId !== undefined
1107
+ ? { connectionId: correlation.connectionId }
1108
+ : {}),
1109
+ ...(correlation.flowId !== undefined ? { flowId: correlation.flowId } : {}),
1110
+ ...(correlation.tenantId !== undefined ? { tenantId: correlation.tenantId } : {}),
1111
+ ...(correlation.requestedProviderId !== undefined
1112
+ ? { requestedProviderId: correlation.requestedProviderId }
1113
+ : {}),
1101
1114
  status,
1102
1115
  ...cost,
1103
1116
  ...(proxy ? { proxy } : {}),
1104
1117
  });
1105
1118
  }
1119
+ const STREAM_CLEANUP_TIMEOUT_MS = 100;
1120
+ const CLIENT_CANCELLED_STATUS = 400;
1121
+ function clientCancelledError() {
1122
+ return new ProviderError("Request stream was cancelled by the client.", {
1123
+ code: "client_cancelled",
1124
+ category: "client_cancelled",
1125
+ retryable: false,
1126
+ });
1127
+ }
1128
+ async function runBestEffortWithTimeout(cleanup) {
1129
+ let timeout;
1130
+ const work = Promise.resolve()
1131
+ .then(cleanup)
1132
+ .catch(() => undefined);
1133
+ try {
1134
+ await Promise.race([
1135
+ work,
1136
+ new Promise((resolve) => {
1137
+ timeout = setTimeout(resolve, STREAM_CLEANUP_TIMEOUT_MS);
1138
+ }),
1139
+ ]);
1140
+ }
1141
+ finally {
1142
+ if (timeout !== undefined)
1143
+ clearTimeout(timeout);
1144
+ }
1145
+ }
1146
+ function traceIdFromTraceparent(headers) {
1147
+ const traceparent = Object.entries(headers).find(([name]) => name.toLowerCase() === "traceparent")?.[1];
1148
+ if (!traceparent)
1149
+ return undefined;
1150
+ const match = /^(00)-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/.exec(traceparent);
1151
+ if (!match)
1152
+ return undefined;
1153
+ const traceId = match[2];
1154
+ const parentId = match[3];
1155
+ if (!traceId || !parentId || /^0+$/.test(traceId) || /^0+$/.test(parentId))
1156
+ return undefined;
1157
+ return traceId;
1158
+ }
1159
+ function requestTraceId(headers, requestId) {
1160
+ return (traceIdFromTraceparent(headers) ??
1161
+ (requestId
1162
+ ? createHash("sha256").update(requestId, "utf8").digest("hex").slice(0, 32)
1163
+ : undefined));
1164
+ }
1165
+ function createRequestScope(input) {
1166
+ const requestCost = startRequestCost();
1167
+ const traceConfig = resolveTraceConfigFromEnv();
1168
+ const proxyTelemetry = new ProxyTelemetryCollector();
1169
+ const details = {
1170
+ route: input.route,
1171
+ requestId: input.requestId,
1172
+ operationId: input.operationId,
1173
+ flowId: input.flowId,
1174
+ headers: input.headers,
1175
+ correlation: { ...(input.correlation ?? {}) },
1176
+ };
1177
+ const traceAttributes = {
1178
+ provider_id: input.provider.id,
1179
+ ...(input.operationId ? { operation_id: input.operationId } : {}),
1180
+ ...(input.flowId ? { flow_id: input.flowId } : {}),
1181
+ ...(input.requestId ? { request_id: input.requestId } : {}),
1182
+ route: input.route,
1183
+ };
1184
+ const initialTraceId = requestTraceId(input.headers, input.requestId);
1185
+ const trace = traceConfig
1186
+ ? createTraceContext({
1187
+ ...resolveServerTraceContextOptions(traceConfig, traceAttributes),
1188
+ ...(initialTraceId ? { traceId: initialTraceId } : {}),
1189
+ })
1190
+ : createTraceContext();
1191
+ let rootRunner = (fn) => fn();
1192
+ let resolveRoot;
1193
+ const rootTerminal = new Promise((resolve) => {
1194
+ resolveRoot = resolve;
1195
+ });
1196
+ let streamingSetupPending = false;
1197
+ let rootSettled = false;
1198
+ let headersSnapshotted = false;
1199
+ let terminalOutcome;
1200
+ let finishedResult;
1201
+ let abortSignal;
1202
+ let abortListener;
1203
+ const streamCleanups = [];
1204
+ const terminalError = (outcome) => {
1205
+ if (outcome.kind === "failed")
1206
+ return outcome.error;
1207
+ if (outcome.kind === "cancelled")
1208
+ return clientCancelledError();
1209
+ return undefined;
1210
+ };
1211
+ const root = trace.span(`request:${input.kind}:${input.route}`, async () => {
1212
+ rootRunner = AsyncLocalStorage.snapshot();
1213
+ const outcome = await rootTerminal;
1214
+ const error = terminalError(outcome);
1215
+ if (error !== undefined)
1216
+ throw error;
1217
+ });
1218
+ void root.catch(() => undefined);
1219
+ const settleRoot = () => {
1220
+ if (!terminalOutcome || streamingSetupPending || rootSettled)
1221
+ return;
1222
+ rootSettled = true;
1223
+ if (abortSignal && abortListener) {
1224
+ abortSignal.removeEventListener("abort", abortListener);
1225
+ }
1226
+ resolveRoot(terminalOutcome);
1227
+ };
1228
+ const runCleanupEntry = (entry) => {
1229
+ entry.promise ??= runBestEffortWithTimeout(entry.cleanup);
1230
+ return entry.promise;
1231
+ };
1232
+ const cleanupStream = async () => {
1233
+ await Promise.all(streamCleanups.map(runCleanupEntry));
1234
+ };
1235
+ const headerSnapshot = (error) => {
1236
+ const providerTelemetryHeader = proxyTelemetry.toHeaderValue();
1237
+ const declaredErrorCode = error === undefined ? undefined : input.declaredErrorCode?.(error);
1238
+ const errorObservability = error === undefined ? undefined : errorObservabilityDetails(error, declaredErrorCode);
1239
+ return {
1240
+ ...(providerTelemetryHeader ? { providerTelemetryHeader } : {}),
1241
+ ...(errorObservability ? { errorObservability } : {}),
1242
+ };
1243
+ };
1244
+ const updateTraceMetadata = () => {
1245
+ if (details.requestId)
1246
+ traceAttributes.request_id = details.requestId;
1247
+ if (details.operationId)
1248
+ traceAttributes.operation_id = details.operationId;
1249
+ if (details.flowId)
1250
+ traceAttributes.flow_id = details.flowId;
1251
+ traceAttributes.route = details.route;
1252
+ const traceId = requestTraceId(details.headers, details.requestId);
1253
+ const sanitizedAttributes = Object.fromEntries(Object.entries(sanitizeTraceAttributes(traceAttributes)).map(([key, value]) => [
1254
+ key,
1255
+ String(value),
1256
+ ]));
1257
+ updateTraceContextExportMetadata(trace, {
1258
+ ...(traceId ? { traceId } : {}),
1259
+ resourceAttributes: sanitizedAttributes,
1260
+ });
1261
+ };
1262
+ const scope = {
1263
+ trace,
1264
+ proxyTelemetry,
1265
+ enrich(enrichment) {
1266
+ if (terminalOutcome)
1267
+ return;
1268
+ if (enrichment.route !== undefined)
1269
+ details.route = enrichment.route;
1270
+ if (enrichment.requestId !== undefined)
1271
+ details.requestId = enrichment.requestId;
1272
+ if (enrichment.operationId !== undefined)
1273
+ details.operationId = enrichment.operationId;
1274
+ if (enrichment.flowId !== undefined)
1275
+ details.flowId = enrichment.flowId;
1276
+ if (enrichment.headers !== undefined)
1277
+ details.headers = enrichment.headers;
1278
+ if (enrichment.correlation !== undefined) {
1279
+ Object.assign(details.correlation, enrichment.correlation);
1280
+ }
1281
+ updateTraceMetadata();
1282
+ },
1283
+ run(fn) {
1284
+ return rootRunner(fn);
1285
+ },
1286
+ async runStreaming(fn) {
1287
+ streamingSetupPending = true;
1288
+ try {
1289
+ return await rootRunner(fn);
1290
+ }
1291
+ finally {
1292
+ streamingSetupPending = false;
1293
+ settleRoot();
1294
+ }
1295
+ },
1296
+ runStreamStep(fn) {
1297
+ return rootRunner(fn);
1298
+ },
1299
+ watchAbort(signal) {
1300
+ if (terminalOutcome)
1301
+ return;
1302
+ abortSignal = signal;
1303
+ abortListener = () => {
1304
+ scope.terminalize({ kind: "cancelled", status: CLIENT_CANCELLED_STATUS });
1305
+ void cleanupStream();
1306
+ };
1307
+ if (signal.aborted)
1308
+ abortListener();
1309
+ else
1310
+ signal.addEventListener("abort", abortListener, { once: true });
1311
+ },
1312
+ registerStreamCleanup(cleanup) {
1313
+ const entry = { cleanup };
1314
+ streamCleanups.push(entry);
1315
+ if (terminalOutcome)
1316
+ void runCleanupEntry(entry);
1317
+ },
1318
+ cleanupStream,
1319
+ snapshotHeaders(error) {
1320
+ headersSnapshotted = true;
1321
+ return headerSnapshot(error);
1322
+ },
1323
+ terminalize(outcome) {
1324
+ if (finishedResult)
1325
+ return finishedResult;
1326
+ terminalOutcome = outcome;
1327
+ const error = terminalError(outcome);
1328
+ finishedResult = {};
1329
+ try {
1330
+ const declaredErrorCode = error === undefined ? undefined : input.declaredErrorCode?.(error);
1331
+ const status = outcome.status ??
1332
+ (error === undefined ? 200 : toStatusCode(error, declaredErrorCode));
1333
+ finishedResult = headerSnapshot(error);
1334
+ const cost = finishRequestCost(requestCost);
1335
+ try {
1336
+ if (error === undefined) {
1337
+ logProviderSuccess(input.logger, input.provider, input.kind, details.route, details.requestId, status, cost, proxyTelemetry, details.correlation);
1338
+ }
1339
+ else {
1340
+ logProviderError(input.logger, input.provider, input.kind, details.route, details.requestId, error, status, cost, declaredErrorCode, proxyTelemetry, finishedResult.errorObservability, details.correlation);
1341
+ }
1342
+ }
1343
+ catch {
1344
+ // Observer callbacks cannot hold the request root open or change the response.
1345
+ }
1346
+ if (!headersSnapshotted) {
1347
+ if (finishedResult.providerTelemetryHeader) {
1348
+ try {
1349
+ input.setHeader?.(PROVIDER_TELEMETRY_HEADER, finishedResult.providerTelemetryHeader);
1350
+ }
1351
+ catch {
1352
+ // Header observers are isolated independently from request settlement.
1353
+ }
1354
+ }
1355
+ if (finishedResult.errorObservability) {
1356
+ try {
1357
+ input.setHeader?.(ERROR_OBSERVABILITY_HEADER, JSON.stringify(finishedResult.errorObservability));
1358
+ }
1359
+ catch {
1360
+ // Header observers are isolated independently from request settlement.
1361
+ }
1362
+ }
1363
+ }
1364
+ }
1365
+ finally {
1366
+ settleRoot();
1367
+ void cleanupStream();
1368
+ }
1369
+ return finishedResult;
1370
+ },
1371
+ };
1372
+ return scope;
1373
+ }
1374
+ function responseWithRequestScopeHeaders(response, finished) {
1375
+ const headers = new Headers(response.headers);
1376
+ headers.delete(PROVIDER_TELEMETRY_HEADER);
1377
+ if (finished.providerTelemetryHeader) {
1378
+ headers.set(PROVIDER_TELEMETRY_HEADER, finished.providerTelemetryHeader);
1379
+ }
1380
+ if (finished.errorObservability) {
1381
+ headers.set(ERROR_OBSERVABILITY_HEADER, JSON.stringify(finished.errorObservability));
1382
+ }
1383
+ return new Response(response.body, {
1384
+ headers,
1385
+ status: response.status,
1386
+ statusText: response.statusText,
1387
+ });
1388
+ }
1389
+ function finalizeRequestResponse(scope, response, outcome) {
1390
+ try {
1391
+ const error = outcome.kind === "failed" ? outcome.error : undefined;
1392
+ const finalResponse = responseWithRequestScopeHeaders(response, scope.snapshotHeaders(error));
1393
+ scope.terminalize(outcome);
1394
+ return finalResponse;
1395
+ }
1396
+ catch (error) {
1397
+ scope.terminalize({ kind: "failed", status: 500, error });
1398
+ throw error;
1399
+ }
1400
+ }
1106
1401
  function toJsonSuccessResponse(result, ctx) {
1107
1402
  if (result instanceof Response) {
1108
1403
  return result;
@@ -1135,45 +1430,40 @@ function isAsyncIterable(value) {
1135
1430
  const iterator = Reflect.get(value, Symbol.asyncIterator);
1136
1431
  return typeof iterator === "function";
1137
1432
  }
1138
- function responseWithCleanup(response, cleanup) {
1433
+ function responseWithCleanup(response, lifecycle) {
1139
1434
  if (!response.body) {
1140
- void cleanup();
1435
+ lifecycle.terminalize({ kind: "completed", status: response.status });
1436
+ void lifecycle.cleanup();
1141
1437
  return response;
1142
1438
  }
1143
1439
  const reader = response.body.getReader();
1144
- let cleaned = false;
1145
- const runCleanup = async () => {
1146
- if (cleaned)
1147
- return;
1148
- cleaned = true;
1149
- await cleanup();
1150
- };
1440
+ let cleanupReason = "request terminalized";
1441
+ lifecycle.registerCleanup(() => lifecycle.runStep(() => reader.cancel(cleanupReason)));
1151
1442
  const body = new ReadableStream({
1152
1443
  async pull(controller) {
1153
1444
  try {
1154
- const { done, value } = await reader.read();
1445
+ const { done, value } = await lifecycle.runStep(() => reader.read());
1155
1446
  if (done) {
1156
1447
  controller.close();
1157
- await runCleanup();
1448
+ lifecycle.terminalize({ kind: "completed", status: response.status });
1449
+ await lifecycle.cleanup();
1158
1450
  return;
1159
1451
  }
1160
1452
  if (value)
1161
1453
  controller.enqueue(value);
1162
1454
  }
1163
1455
  catch (error) {
1164
- await runCleanup();
1456
+ lifecycle.terminalize({ kind: "failed", error });
1457
+ await lifecycle.cleanup();
1165
1458
  controller.error(error);
1166
1459
  }
1167
1460
  },
1168
1461
  async cancel(reason) {
1169
- try {
1170
- await reader.cancel(reason);
1171
- }
1172
- finally {
1173
- await runCleanup();
1174
- }
1462
+ cleanupReason = reason;
1463
+ lifecycle.terminalize({ kind: "cancelled", status: CLIENT_CANCELLED_STATUS });
1464
+ await lifecycle.cleanup();
1175
1465
  },
1176
- });
1466
+ }, { highWaterMark: 0 });
1177
1467
  return new Response(body, {
1178
1468
  headers: response.headers,
1179
1469
  status: response.status,
@@ -1215,37 +1505,39 @@ function assertStreamPayloadWithinLimit(actualBytes, maxBytes, kind) {
1215
1505
  : "Emit smaller stream chunks or increase transport.maxChunkBytes.",
1216
1506
  });
1217
1507
  }
1218
- function toSseResponse(operation, result, cleanup, requestId) {
1508
+ function toSseResponse(operation, result, lifecycle, requestId) {
1219
1509
  const encoder = new TextEncoder();
1220
1510
  const iterator = result[Symbol.asyncIterator]();
1511
+ let cleanupReason;
1512
+ lifecycle.registerCleanup(() => lifecycle.runStep(async () => {
1513
+ await iterator.return?.(cleanupReason);
1514
+ }));
1221
1515
  const transport = getSseTransport(operation);
1222
1516
  let done = false;
1223
- let cleaned = false;
1224
- const runCleanup = async () => {
1225
- if (cleaned)
1226
- return;
1227
- cleaned = true;
1228
- await cleanup();
1229
- };
1230
1517
  const body = new ReadableStream({
1231
1518
  async pull(controller) {
1232
1519
  try {
1233
1520
  if (done) {
1234
1521
  controller.close();
1235
- await runCleanup();
1522
+ lifecycle.terminalize({ kind: "completed", status: 200 });
1523
+ await lifecycle.cleanup();
1236
1524
  return;
1237
1525
  }
1238
- const next = await iterator.next();
1526
+ const next = await lifecycle.runStep(() => iterator.next());
1239
1527
  if (next.done) {
1240
1528
  done = true;
1241
1529
  controller.close();
1242
- await runCleanup();
1530
+ lifecycle.terminalize({ kind: "completed", status: 200 });
1531
+ await lifecycle.cleanup();
1243
1532
  return;
1244
1533
  }
1245
- const validated = await validateSseEvent(operation, next.value);
1246
- const encodedEvent = encodeSseEvent(validated);
1247
- const encodedBytes = encoder.encode(encodedEvent);
1248
- assertStreamPayloadWithinLimit(encodedBytes.byteLength, transport?.maxEventBytes, "event");
1534
+ const encodedBytes = await lifecycle.runStep(async () => {
1535
+ const validated = await validateSseEvent(operation, next.value);
1536
+ const encodedEvent = encodeSseEvent(validated);
1537
+ const bytes = encoder.encode(encodedEvent);
1538
+ assertStreamPayloadWithinLimit(bytes.byteLength, transport?.maxEventBytes, "event");
1539
+ return bytes;
1540
+ });
1249
1541
  controller.enqueue(encodedBytes);
1250
1542
  }
1251
1543
  catch (error) {
@@ -1255,18 +1547,16 @@ function toSseResponse(operation, result, cleanup, requestId) {
1255
1547
  }))));
1256
1548
  controller.close();
1257
1549
  done = true;
1258
- await runCleanup();
1550
+ lifecycle.terminalize({ kind: "failed", error });
1551
+ await lifecycle.cleanup();
1259
1552
  }
1260
1553
  },
1261
1554
  async cancel(reason) {
1262
- try {
1263
- await iterator.return?.(reason);
1264
- }
1265
- finally {
1266
- await runCleanup();
1267
- }
1555
+ cleanupReason = reason;
1556
+ lifecycle.terminalize({ kind: "cancelled", status: CLIENT_CANCELLED_STATUS });
1557
+ await lifecycle.cleanup();
1268
1558
  },
1269
- });
1559
+ }, { highWaterMark: 0 });
1270
1560
  return new Response(body, {
1271
1561
  headers: {
1272
1562
  "Cache-Control": "no-cache, no-transform",
@@ -1301,10 +1591,9 @@ function enforceStreamChunkLimit(body, maxChunkBytes) {
1301
1591
  },
1302
1592
  });
1303
1593
  }
1304
- function toStreamingResponse(operation, result, cleanup, requestId) {
1594
+ function toStreamingResponse(operation, result, lifecycle, requestId) {
1305
1595
  const transport = operation.transport?.kind ?? "json";
1306
1596
  if (transport === "sse" && (result instanceof Response || result instanceof ReadableStream)) {
1307
- void cleanup();
1308
1597
  throw new ProviderError("SSE operations must return an AsyncIterable of typed stream.event(...) values.", {
1309
1598
  code: "SSE_RESULT_UNSUPPORTED",
1310
1599
  category: "output_validation",
@@ -1319,9 +1608,9 @@ function toStreamingResponse(operation, result, cleanup, requestId) {
1319
1608
  headers: result.headers,
1320
1609
  status: result.status,
1321
1610
  statusText: result.statusText,
1322
- }), cleanup);
1611
+ }), lifecycle);
1323
1612
  }
1324
- return responseWithCleanup(result, cleanup);
1613
+ return responseWithCleanup(result, lifecycle);
1325
1614
  }
1326
1615
  if (result instanceof ReadableStream) {
1327
1616
  const httpTransport = getHttpStreamTransport(operation);
@@ -1336,12 +1625,11 @@ function toStreamingResponse(operation, result, cleanup, requestId) {
1336
1625
  ? (operation.transport.contentType ?? "application/octet-stream")
1337
1626
  : "application/octet-stream",
1338
1627
  },
1339
- }), cleanup);
1628
+ }), lifecycle);
1340
1629
  }
1341
1630
  if (transport === "sse" && isAsyncIterable(result)) {
1342
- return toSseResponse(operation, result, cleanup, requestId);
1631
+ return toSseResponse(operation, result, lifecycle, requestId);
1343
1632
  }
1344
- void cleanup();
1345
1633
  throw new ProviderError(`Streaming operation returned unsupported result for transport "${transport}"`, {
1346
1634
  code: "STREAM_RESULT_UNSUPPORTED",
1347
1635
  fix: "Return an AsyncIterable of stream.event(...) values, a ReadableStream, or a Response from streaming operations.",
@@ -1419,8 +1707,8 @@ function withAuthRequestHeaders(request, headers) {
1419
1707
  },
1420
1708
  };
1421
1709
  }
1422
- async function handleOperation(provider, request, operationId, options, state = createUnsupportedProviderRuntimeState(), proxyTelemetry, signal) {
1423
- const ctx = createProviderContext(provider, request, operationId, options, state, proxyTelemetry, signal);
1710
+ async function handleOperation(provider, request, operationId, options, state = createUnsupportedProviderRuntimeState(), scope, signal) {
1711
+ const ctx = createProviderContext(provider, request, operationId, options, state, scope, signal);
1424
1712
  const operation = provider.operations[operationId];
1425
1713
  const streaming = operation?.transport?.kind && operation.transport.kind !== "json";
1426
1714
  let cleanupCalled = false;
@@ -1445,6 +1733,21 @@ async function handleOperation(provider, request, operationId, options, state =
1445
1733
  }
1446
1734
  }
1447
1735
  };
1736
+ const streamLifecycle = {
1737
+ runStep(fn) {
1738
+ return scope.runStreamStep(fn);
1739
+ },
1740
+ registerCleanup(streamCleanup) {
1741
+ scope.registerStreamCleanup(streamCleanup);
1742
+ },
1743
+ terminalize(outcome) {
1744
+ scope.terminalize(outcome);
1745
+ },
1746
+ cleanup() {
1747
+ return scope.cleanupStream();
1748
+ },
1749
+ };
1750
+ scope.registerStreamCleanup(() => scope.runStreamStep(cleanup));
1448
1751
  try {
1449
1752
  const result = options.operationExecutor
1450
1753
  ? await options.operationExecutor({
@@ -1458,7 +1761,7 @@ async function handleOperation(provider, request, operationId, options, state =
1458
1761
  env: createEnvContext(providerSecretNames(provider)),
1459
1762
  });
1460
1763
  if (streaming && operation) {
1461
- return toStreamingResponse(operation, result, cleanup, request.requestId);
1764
+ return toStreamingResponse(operation, result, streamLifecycle, request.requestId);
1462
1765
  }
1463
1766
  return toJsonSuccessResponse(result, ctx);
1464
1767
  }
@@ -1471,19 +1774,7 @@ async function handleOperation(provider, request, operationId, options, state =
1471
1774
  await cleanup();
1472
1775
  }
1473
1776
  }
1474
- function responseWithProviderTelemetry(response, proxyTelemetry) {
1475
- const headerValue = proxyTelemetry?.toHeaderValue();
1476
- const headers = new Headers(response.headers);
1477
- headers.delete(PROVIDER_TELEMETRY_HEADER);
1478
- if (headerValue)
1479
- headers.set(PROVIDER_TELEMETRY_HEADER, headerValue);
1480
- return new Response(response.body, {
1481
- headers,
1482
- status: response.status,
1483
- statusText: response.statusText,
1484
- });
1485
- }
1486
- async function handleAuthFlow(provider, request, route, options, state, proxyTelemetry, signal) {
1777
+ async function handleAuthFlow(provider, request, route, options, state, scope, signal) {
1487
1778
  const flow = provider.auth?.flow;
1488
1779
  if (!flow) {
1489
1780
  throw new ProviderError("Auth flow is not configured", {
@@ -1495,7 +1786,7 @@ async function handleAuthFlow(provider, request, route, options, state, proxyTel
1495
1786
  // any flow code runs instead of at whatever point the ceremony first reads
1496
1787
  // the env. `abort` stays exempt: a user must always be able to cancel a
1497
1788
  // stranded flow even when provisioning is broken.
1498
- const { context, getPatch } = createAuthFlowContext(provider, request, options, state, proxyTelemetry, signal);
1789
+ const { context, getPatch } = createAuthFlowContext(provider, request, options, state, scope, signal);
1499
1790
  try {
1500
1791
  if (route !== "abort") {
1501
1792
  assertRequiredSecretsPresent(provider, context.env);
@@ -1723,7 +2014,15 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
1723
2014
  let rawBody;
1724
2015
  let operationId;
1725
2016
  const operation = "stateful-internal";
1726
- const requestCost = startRequestCost();
2017
+ const requestScope = createRequestScope({
2018
+ provider,
2019
+ kind: "operation",
2020
+ route: operation,
2021
+ headers: Object.fromEntries(c.req.raw.headers.entries()),
2022
+ logger,
2023
+ setHeader: (name, value) => c.header(name, value),
2024
+ declaredErrorCode: (error) => declaredErrorCodeFor(error, operationId, operationErrorCodes),
2025
+ });
1727
2026
  try {
1728
2027
  if (!options.internalOperationExecutor) {
1729
2028
  throw new ProviderError("Stateful internal operation executor is not configured.", {
@@ -1808,20 +2107,32 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
1808
2107
  }
1809
2108
  const request = operationRequestFromForwardingEnvelope(envelope);
1810
2109
  operationId = envelope.operationId;
1811
- const ctx = createProviderContext(provider, request, operationId, options, state, undefined, signal);
1812
- if (deadlineAtMs !== undefined && deadlineAtMs <= Date.now()) {
1813
- throw new StatefulRoutingDeadlineError(envelope.requestId, envelope.deadlineAt);
1814
- }
1815
- const output = await options.internalOperationExecutor({
1816
- provider,
2110
+ requestScope.enrich({
2111
+ route: operationId,
2112
+ requestId: request.requestId,
1817
2113
  operationId,
1818
- ctx,
1819
- request,
1820
- internalStatefulForward: envelope,
1821
- signal,
2114
+ headers: request.headers ?? {},
2115
+ correlation: { connectionId: envelope.connectionId },
2116
+ });
2117
+ const response = await requestScope.run(async () => {
2118
+ const ctx = createProviderContext(provider, request, operationId, options, state, requestScope, signal);
2119
+ if (deadlineAtMs !== undefined && deadlineAtMs <= Date.now()) {
2120
+ throw new StatefulRoutingDeadlineError(envelope.requestId, envelope.deadlineAt);
2121
+ }
2122
+ const output = await options.internalOperationExecutor({
2123
+ provider,
2124
+ operationId: operationId,
2125
+ ctx,
2126
+ request,
2127
+ internalStatefulForward: envelope,
2128
+ signal,
2129
+ });
2130
+ return c.json({ data: output });
2131
+ });
2132
+ return finalizeRequestResponse(requestScope, response, {
2133
+ kind: "completed",
2134
+ status: 200,
1822
2135
  });
1823
- logProviderSuccess(logger, provider, "operation", operationId || operation, request.requestId, 200, finishRequestCost(requestCost));
1824
- return c.json({ data: output });
1825
2136
  }
1826
2137
  catch (error) {
1827
2138
  const declaredErrorCode = declaredErrorCodeFor(error, operationId, operationErrorCodes);
@@ -1831,16 +2142,32 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
1831
2142
  c.header("Retry-After", String(STATEFUL_FORWARDING_REPLAY_RETRY_AFTER_SECONDS));
1832
2143
  }
1833
2144
  const requestId = extractRequestId(rawBody);
2145
+ requestScope.enrich({
2146
+ ...(operationId ? { route: operationId, operationId } : {}),
2147
+ ...(requestId ? { requestId } : {}),
2148
+ });
1834
2149
  const observabilityDetails = errorObservabilityDetails(error, declaredErrorCode);
1835
- logProviderError(logger, provider, "operation", operationId || operation, requestId, error, status, finishRequestCost(requestCost), declaredErrorCode, undefined, observabilityDetails);
1836
- return responseWithErrorObservability(c.json(toErrorResponse(error, requestId, observabilityDetails), status), observabilityDetails);
2150
+ const response = c.json(toErrorResponse(error, requestId, observabilityDetails), status);
2151
+ return finalizeRequestResponse(requestScope, response, {
2152
+ kind: "failed",
2153
+ status,
2154
+ error,
2155
+ });
1837
2156
  }
1838
2157
  });
1839
2158
  app.post("/v1/:operation", async (c) => {
1840
2159
  let rawBody;
1841
2160
  const operation = c.req.param("operation");
1842
- const proxyTelemetry = new ProxyTelemetryCollector();
1843
- const requestCost = startRequestCost();
2161
+ const requestScope = createRequestScope({
2162
+ provider,
2163
+ kind: "operation",
2164
+ route: operation,
2165
+ operationId: operation,
2166
+ headers: Object.fromEntries(c.req.raw.headers.entries()),
2167
+ logger,
2168
+ setHeader: (name, value) => c.header(name, value),
2169
+ declaredErrorCode: (error) => declaredErrorCodeFor(error, operation, operationErrorCodes),
2170
+ });
1844
2171
  try {
1845
2172
  rawBody = await c.req.raw
1846
2173
  .clone()
@@ -1849,179 +2176,107 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
1849
2176
  const body = OperationRequestSchema.parse(rawBody);
1850
2177
  const requestHeaders = Object.fromEntries(c.req.raw.headers.entries());
1851
2178
  body.headers = { ...requestHeaders, ...body.headers };
1852
- const response = await handleOperation(provider, body, operation, options, state, proxyTelemetry, c.req.raw.signal);
1853
- if (response instanceof Response) {
1854
- logProviderSuccess(logger, provider, "operation", operation, body.requestId, response.status, finishRequestCost(requestCost), proxyTelemetry);
1855
- return responseWithProviderTelemetry(response, proxyTelemetry);
1856
- }
1857
- const telemetryHeader = proxyTelemetry.toHeaderValue();
1858
- if (telemetryHeader)
1859
- c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1860
- logProviderSuccess(logger, provider, "operation", operation, body.requestId, 200, finishRequestCost(requestCost), proxyTelemetry);
1861
- return c.json(response);
2179
+ requestScope.enrich({
2180
+ requestId: body.requestId,
2181
+ headers: body.headers,
2182
+ correlation: { connectionId: resolveOperationConnectionId(body) },
2183
+ });
2184
+ const streaming = provider.operations[operation]?.transport?.kind
2185
+ ? provider.operations[operation]?.transport?.kind !== "json"
2186
+ : false;
2187
+ if (streaming)
2188
+ requestScope.watchAbort(c.req.raw.signal);
2189
+ const execute = async () => {
2190
+ const handled = await handleOperation(provider, body, operation, options, state, requestScope, c.req.raw.signal);
2191
+ const response = handled instanceof Response ? handled : c.json(handled);
2192
+ return streaming
2193
+ ? responseWithRequestScopeHeaders(response, requestScope.snapshotHeaders())
2194
+ : response;
2195
+ };
2196
+ const response = streaming
2197
+ ? await requestScope.runStreaming(execute)
2198
+ : await requestScope.run(execute);
2199
+ if (streaming)
2200
+ return response;
2201
+ return finalizeRequestResponse(requestScope, response, {
2202
+ kind: "completed",
2203
+ status: response.status,
2204
+ });
1862
2205
  }
1863
2206
  catch (error) {
1864
2207
  const declaredErrorCode = declaredErrorCodeFor(error, operation, operationErrorCodes);
1865
2208
  const status = toStatusCode(error, declaredErrorCode);
1866
2209
  const requestId = extractRequestId(rawBody);
2210
+ requestScope.enrich({ ...(requestId ? { requestId } : {}) });
1867
2211
  const observabilityDetails = errorObservabilityDetails(error, declaredErrorCode);
1868
- logProviderError(logger, provider, "operation", operation, requestId, error, status, finishRequestCost(requestCost), declaredErrorCode, proxyTelemetry, observabilityDetails);
1869
- const telemetryHeader = proxyTelemetry.toHeaderValue();
1870
- if (telemetryHeader)
1871
- c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1872
- return responseWithErrorObservability(c.json(toErrorResponse(error, requestId, observabilityDetails), status), observabilityDetails);
1873
- }
1874
- });
1875
- app.post("/auth/start", async (c) => {
1876
- let rawBody;
1877
- const proxyTelemetry = new ProxyTelemetryCollector();
1878
- const requestCost = startRequestCost();
1879
- try {
1880
- rawBody = await c.req.raw
1881
- .clone()
1882
- .json()
1883
- .catch(() => undefined);
1884
- const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1885
- const response = await handleAuthFlow(provider, body, "start", options, state, proxyTelemetry, c.req.raw.signal);
1886
- logProviderSuccess(logger, provider, "auth", "start", body.requestId, response instanceof Response ? response.status : 200, finishRequestCost(requestCost), proxyTelemetry);
1887
- if (response instanceof Response)
1888
- return responseWithProviderTelemetry(response, proxyTelemetry);
1889
- const telemetryHeader = proxyTelemetry.toHeaderValue();
1890
- if (telemetryHeader)
1891
- c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1892
- return c.json(response);
1893
- }
1894
- catch (error) {
1895
- const status = toStatusCode(error);
1896
- const requestId = extractRequestId(rawBody);
1897
- const observabilityDetails = errorObservabilityDetails(error);
1898
- logProviderError(logger, provider, "auth", "start", requestId, error, status, finishRequestCost(requestCost), undefined, proxyTelemetry, observabilityDetails);
1899
- const telemetryHeader = proxyTelemetry.toHeaderValue();
1900
- if (telemetryHeader)
1901
- c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1902
- return responseWithErrorObservability(c.json(toErrorResponse(error, requestId, observabilityDetails), status), observabilityDetails);
1903
- }
1904
- });
1905
- app.post("/auth/continue", async (c) => {
1906
- let rawBody;
1907
- const proxyTelemetry = new ProxyTelemetryCollector();
1908
- const requestCost = startRequestCost();
1909
- try {
1910
- rawBody = await c.req.raw
1911
- .clone()
1912
- .json()
1913
- .catch(() => undefined);
1914
- const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1915
- const response = await handleAuthFlow(provider, body, "continue", options, state, proxyTelemetry, c.req.raw.signal);
1916
- logProviderSuccess(logger, provider, "auth", "continue", body.requestId, response instanceof Response ? response.status : 200, finishRequestCost(requestCost), proxyTelemetry);
1917
- if (response instanceof Response)
1918
- return responseWithProviderTelemetry(response, proxyTelemetry);
1919
- const telemetryHeader = proxyTelemetry.toHeaderValue();
1920
- if (telemetryHeader)
1921
- c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1922
- return c.json(response);
1923
- }
1924
- catch (error) {
1925
- const status = toStatusCode(error);
1926
- const requestId = extractRequestId(rawBody);
1927
- const observabilityDetails = errorObservabilityDetails(error);
1928
- logProviderError(logger, provider, "auth", "continue", requestId, error, status, finishRequestCost(requestCost), undefined, proxyTelemetry, observabilityDetails);
1929
- const telemetryHeader = proxyTelemetry.toHeaderValue();
1930
- if (telemetryHeader)
1931
- c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1932
- return responseWithErrorObservability(c.json(toErrorResponse(error, requestId, observabilityDetails), status), observabilityDetails);
1933
- }
1934
- });
1935
- app.post("/auth/poll", async (c) => {
1936
- let rawBody;
1937
- const proxyTelemetry = new ProxyTelemetryCollector();
1938
- const requestCost = startRequestCost();
1939
- try {
1940
- rawBody = await c.req.raw
1941
- .clone()
1942
- .json()
1943
- .catch(() => undefined);
1944
- const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1945
- const response = await handleAuthFlow(provider, body, "poll", options, state, proxyTelemetry, c.req.raw.signal);
1946
- logProviderSuccess(logger, provider, "auth", "poll", body.requestId, response instanceof Response ? response.status : 200, finishRequestCost(requestCost), proxyTelemetry);
1947
- if (response instanceof Response)
1948
- return responseWithProviderTelemetry(response, proxyTelemetry);
1949
- const telemetryHeader = proxyTelemetry.toHeaderValue();
1950
- if (telemetryHeader)
1951
- c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1952
- return c.json(response);
1953
- }
1954
- catch (error) {
1955
- const status = toStatusCode(error);
1956
- const requestId = extractRequestId(rawBody);
1957
- const observabilityDetails = errorObservabilityDetails(error);
1958
- logProviderError(logger, provider, "auth", "poll", requestId, error, status, finishRequestCost(requestCost), undefined, proxyTelemetry, observabilityDetails);
1959
- const telemetryHeader = proxyTelemetry.toHeaderValue();
1960
- if (telemetryHeader)
1961
- c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1962
- return responseWithErrorObservability(c.json(toErrorResponse(error, requestId, observabilityDetails), status), observabilityDetails);
1963
- }
1964
- });
1965
- app.post("/auth/refresh", async (c) => {
1966
- let rawBody;
1967
- const proxyTelemetry = new ProxyTelemetryCollector();
1968
- const requestCost = startRequestCost();
1969
- try {
1970
- rawBody = await c.req.raw
1971
- .clone()
1972
- .json()
1973
- .catch(() => undefined);
1974
- const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1975
- const response = await handleAuthFlow(provider, body, "refresh", options, state, proxyTelemetry, c.req.raw.signal);
1976
- logProviderSuccess(logger, provider, "auth", "refresh", body.requestId, response instanceof Response ? response.status : 200, finishRequestCost(requestCost), proxyTelemetry);
1977
- if (response instanceof Response)
1978
- return responseWithProviderTelemetry(response, proxyTelemetry);
1979
- const telemetryHeader = proxyTelemetry.toHeaderValue();
1980
- if (telemetryHeader)
1981
- c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1982
- return c.json(response);
1983
- }
1984
- catch (error) {
1985
- const status = toStatusCode(error);
1986
- const requestId = extractRequestId(rawBody);
1987
- const observabilityDetails = errorObservabilityDetails(error);
1988
- logProviderError(logger, provider, "auth", "refresh", requestId, error, status, finishRequestCost(requestCost), undefined, proxyTelemetry, observabilityDetails);
1989
- const telemetryHeader = proxyTelemetry.toHeaderValue();
1990
- if (telemetryHeader)
1991
- c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1992
- return responseWithErrorObservability(c.json(toErrorResponse(error, requestId, observabilityDetails), status), observabilityDetails);
1993
- }
1994
- });
1995
- app.post("/auth/disconnect", async (c) => {
1996
- let rawBody;
1997
- const proxyTelemetry = new ProxyTelemetryCollector();
1998
- const requestCost = startRequestCost();
1999
- try {
2000
- rawBody = await c.req.raw
2001
- .clone()
2002
- .json()
2003
- .catch(() => undefined);
2004
- const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
2005
- const response = await handleAuthFlow(provider, body, "abort", options, state, proxyTelemetry, c.req.raw.signal);
2006
- logProviderSuccess(logger, provider, "auth", "disconnect", body.requestId, response instanceof Response ? response.status : 200, finishRequestCost(requestCost), proxyTelemetry);
2007
- if (response instanceof Response)
2008
- return responseWithProviderTelemetry(response, proxyTelemetry);
2009
- const telemetryHeader = proxyTelemetry.toHeaderValue();
2010
- if (telemetryHeader)
2011
- c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
2012
- return c.json(response);
2013
- }
2014
- catch (error) {
2015
- const status = toStatusCode(error);
2016
- const requestId = extractRequestId(rawBody);
2017
- const observabilityDetails = errorObservabilityDetails(error);
2018
- logProviderError(logger, provider, "auth", "disconnect", requestId, error, status, finishRequestCost(requestCost), undefined, proxyTelemetry, observabilityDetails);
2019
- const telemetryHeader = proxyTelemetry.toHeaderValue();
2020
- if (telemetryHeader)
2021
- c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
2022
- return responseWithErrorObservability(c.json(toErrorResponse(error, requestId, observabilityDetails), status), observabilityDetails);
2212
+ const response = c.json(toErrorResponse(error, requestId, observabilityDetails), status);
2213
+ return finalizeRequestResponse(requestScope, response, {
2214
+ kind: "failed",
2215
+ status,
2216
+ error,
2217
+ });
2023
2218
  }
2024
2219
  });
2220
+ const authRoutes = [
2221
+ { path: "/auth/start", flowRoute: "start", logRoute: "start" },
2222
+ { path: "/auth/continue", flowRoute: "continue", logRoute: "continue" },
2223
+ { path: "/auth/poll", flowRoute: "poll", logRoute: "poll" },
2224
+ { path: "/auth/refresh", flowRoute: "refresh", logRoute: "refresh" },
2225
+ { path: "/auth/disconnect", flowRoute: "abort", logRoute: "disconnect" },
2226
+ ];
2227
+ for (const { path, flowRoute, logRoute } of authRoutes) {
2228
+ app.post(path, async (c) => {
2229
+ let rawBody;
2230
+ const requestScope = createRequestScope({
2231
+ provider,
2232
+ kind: "auth",
2233
+ route: logRoute,
2234
+ headers: Object.fromEntries(c.req.raw.headers.entries()),
2235
+ logger,
2236
+ setHeader: (name, value) => c.header(name, value),
2237
+ });
2238
+ try {
2239
+ rawBody = await c.req.raw
2240
+ .clone()
2241
+ .json()
2242
+ .catch(() => undefined);
2243
+ const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
2244
+ requestScope.enrich({
2245
+ requestId: body.requestId,
2246
+ flowId: body.flowId,
2247
+ headers: body.headers ?? {},
2248
+ correlation: {
2249
+ connectionId: resolveOperationConnectionId(body),
2250
+ flowId: body.flowId,
2251
+ tenantId: body.tenantId,
2252
+ requestedProviderId: body.providerId !== undefined && body.providerId !== provider.id
2253
+ ? body.providerId
2254
+ : undefined,
2255
+ },
2256
+ });
2257
+ const response = await requestScope.run(async () => {
2258
+ const handled = await handleAuthFlow(provider, body, flowRoute, options, state, requestScope, c.req.raw.signal);
2259
+ return handled instanceof Response ? handled : c.json(handled);
2260
+ });
2261
+ return finalizeRequestResponse(requestScope, response, {
2262
+ kind: "completed",
2263
+ status: response.status,
2264
+ });
2265
+ }
2266
+ catch (error) {
2267
+ const status = toStatusCode(error);
2268
+ const requestId = extractRequestId(rawBody);
2269
+ requestScope.enrich({ ...(requestId ? { requestId } : {}) });
2270
+ const observabilityDetails = errorObservabilityDetails(error);
2271
+ const response = c.json(toErrorResponse(error, requestId, observabilityDetails), status);
2272
+ return finalizeRequestResponse(requestScope, response, {
2273
+ kind: "failed",
2274
+ status,
2275
+ error,
2276
+ });
2277
+ }
2278
+ });
2279
+ }
2025
2280
  return app;
2026
2281
  }
2027
2282
  function validateStatefulServerConfig(options) {