@apifuse/provider-sdk 2.2.0-beta.40 → 2.2.0-beta.41

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 (44) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/bin/apifuse-check.ts +61 -0
  3. package/bin/apifuse-migrate-shape.ts +84 -0
  4. package/bin/apifuse-submit-check.ts +1760 -222
  5. package/dist/cli/commands.d.ts +1 -1
  6. package/dist/cli/commands.js +8 -0
  7. package/dist/cli/create.js +6 -0
  8. package/dist/cli/migrate-provider-shape.d.ts +52 -0
  9. package/dist/cli/migrate-provider-shape.js +515 -0
  10. package/dist/cli/templates/provider/provider.json.tpl +6 -0
  11. package/dist/contract.js +1 -0
  12. package/dist/define.js +22 -1
  13. package/dist/error-observability.d.ts +7 -0
  14. package/dist/error-observability.js +61 -0
  15. package/dist/errors.d.ts +15 -0
  16. package/dist/fixture-sanitization.js +13 -3
  17. package/dist/index.d.ts +1 -1
  18. package/dist/provider.d.ts +1 -1
  19. package/dist/runtime/executor.js +11 -1
  20. package/dist/server/error-observability.d.ts +1 -0
  21. package/dist/server/error-observability.js +1 -0
  22. package/dist/server/index.d.ts +2 -1
  23. package/dist/server/self-test.js +3 -0
  24. package/dist/server/serve-implementation.d.ts +12 -0
  25. package/dist/server/serve-implementation.js +135 -66
  26. package/dist/types.d.ts +18 -10
  27. package/package.json +3 -3
  28. package/src/cli/commands.ts +10 -0
  29. package/src/cli/create.ts +6 -0
  30. package/src/cli/migrate-provider-shape.ts +701 -0
  31. package/src/cli/templates/provider/provider.json.tpl +6 -0
  32. package/src/contract.ts +1 -0
  33. package/src/define.ts +33 -1
  34. package/src/error-observability.ts +64 -0
  35. package/src/errors.ts +16 -0
  36. package/src/fixture-sanitization.ts +19 -3
  37. package/src/index.ts +1 -0
  38. package/src/provider.ts +1 -0
  39. package/src/runtime/executor.ts +13 -1
  40. package/src/server/error-observability.ts +1 -0
  41. package/src/server/index.ts +2 -0
  42. package/src/server/self-test.ts +5 -0
  43. package/src/server/serve-implementation.ts +172 -84
  44. package/src/types.ts +38 -27
@@ -7,6 +7,7 @@ import { Hono } from "hono";
7
7
  import { z } from "zod";
8
8
  import { AuthAbortError, createAuthFlowHelpers } from "../auth.js";
9
9
  import { validateFailClosedDeclaration } from "../declaration-validation.js";
10
+ import { safeProviderErrorObservability } from "../error-observability.js";
10
11
  import {
11
12
  SDK_OWNED_PROVIDER_ERROR_CODES,
12
13
  SDK_RUNTIME_OWNED_ERROR_CODES,
@@ -19,7 +20,10 @@ import {
19
20
  isTransportError,
20
21
  isValidationError,
21
22
  ProviderError,
23
+ type ProviderErrorObservability,
24
+ type ProviderErrorOptions,
22
25
  } from "../errors.js";
26
+ import { sanitizeDiagnosticText } from "../fixture-sanitization.js";
23
27
  import {
24
28
  loadProviderLocaleCatalogs,
25
29
  localizeAuthTurn,
@@ -140,7 +144,36 @@ export type ErrorObservabilityDetails = {
140
144
  taxonomyVersion: string;
141
145
  retryable: boolean;
142
146
  upstreamStatus?: number;
147
+ providerObservability?: ProviderErrorObservability;
143
148
  };
149
+
150
+ // Provider errors normally expose `options` through an own data property, but
151
+ // providers can replace that property with a throwing accessor. Provider-controlled
152
+ // accessor failures are absorbed into canonical classification rather than
153
+ // propagated. By contract, read `options` directly instead of the public
154
+ // `fix`/`details` convenience getters; provider-controlled accessors are not trusted.
155
+ function providerErrorOption<K extends keyof ProviderErrorOptions>(
156
+ error: unknown,
157
+ key: K,
158
+ ): ProviderErrorOptions[K] | undefined {
159
+ if (!isProviderError(error)) return undefined;
160
+ try {
161
+ return (error as ProviderError).options?.[key] as ProviderErrorOptions[K] | undefined;
162
+ } catch {
163
+ return undefined;
164
+ }
165
+ }
166
+
167
+ function providerErrorCode(error: unknown): string | undefined {
168
+ if (!isProviderError(error)) return undefined;
169
+ try {
170
+ const code: unknown = (error as ProviderError).code;
171
+ return typeof code === "string" ? code : undefined;
172
+ } catch {
173
+ return undefined;
174
+ }
175
+ }
176
+
144
177
  const AUTH_FLOW_LOCALES = ["en", "ko", "ja"] as const;
145
178
  const retryResponseMeta = new WeakMap<ProviderContext, HttpRetrySummary>();
146
179
  const STATEFUL_INTERNAL_OPERATIONS_ROUTE = "/__apifuse/stateful/operations";
@@ -989,6 +1022,8 @@ export type ProviderServerLogEvent =
989
1022
  errorCategory?: ProviderErrorCategory;
990
1023
  taxonomyVersion?: string;
991
1024
  retryable?: boolean;
1025
+ providerObservability?: ProviderErrorObservability;
1026
+ causeChain?: ProviderErrorCauseFrame[];
992
1027
  signal?: "unregistered_provider_error_code";
993
1028
  signalFix?: string;
994
1029
  issues?: Array<{ path: string; code: string; message: string }>;
@@ -1135,8 +1170,9 @@ function zodDetails(error: z.ZodError): Array<{
1135
1170
  function publicErrorSource(error: unknown, category: ProviderErrorCategory): ProviderErrorSource {
1136
1171
  if (error instanceof StatefulRoutingDeadlineError) return "apifuse";
1137
1172
  if (isProviderError(error)) {
1138
- if (error.code === MISSING_SECRET_CODE) return "apifuse";
1139
- if (error.code === "UPSTREAM_ERROR" || error.code === "BLOCKED") {
1173
+ const code = providerErrorCode(error);
1174
+ if (code === MISSING_SECRET_CODE) return "apifuse";
1175
+ if (code === "UPSTREAM_ERROR" || code === "BLOCKED") {
1140
1176
  return "upstream_failure";
1141
1177
  }
1142
1178
  }
@@ -1145,10 +1181,10 @@ function publicErrorSource(error: unknown, category: ProviderErrorCategory): Pro
1145
1181
 
1146
1182
  function toErrorResponse(
1147
1183
  error: unknown,
1148
- requestId?: string,
1149
- declaredErrorCode?: OperationErrorCode,
1184
+ requestId: string | undefined,
1185
+ observabilityDetails: ErrorObservabilityDetails,
1150
1186
  ): OperationErrorResponse {
1151
- const observability = errorObservabilityDetails(error, declaredErrorCode);
1187
+ const observability = observabilityDetails;
1152
1188
  const source = publicErrorSource(error, observability.category);
1153
1189
  if (error instanceof StatefulRoutingDeadlineError) {
1154
1190
  return {
@@ -1163,15 +1199,15 @@ function toErrorResponse(
1163
1199
  }
1164
1200
 
1165
1201
  if (isProviderError(error)) {
1166
- const details = error.details;
1202
+ const details = providerErrorOption(error, "details");
1167
1203
  return {
1168
1204
  error: {
1169
- code: error.code ?? "provider_error",
1205
+ code: providerErrorCode(error) ?? "provider_error",
1170
1206
  message: publicProviderErrorMessage(error),
1171
1207
  ...(requestId ? { requestId } : {}),
1172
1208
  retryable: observability.retryable,
1173
1209
  source,
1174
- ...(error.fix ? { fix: error.fix } : {}),
1210
+ ...(providerErrorOption(error, "fix") ? { fix: providerErrorOption(error, "fix") } : {}),
1175
1211
  ...(details !== undefined ? { details } : {}),
1176
1212
  },
1177
1213
  };
@@ -1231,9 +1267,9 @@ function providerObservabilityDetails(
1231
1267
  // signal for exactly the retryOnAuthRefresh operations it is meant to enable.
1232
1268
  if (isSessionExpiredError(error)) {
1233
1269
  return {
1234
- category: error.options?.category ?? "credential_expired",
1270
+ category: providerErrorOption(error, "category") ?? "credential_expired",
1235
1271
  taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
1236
- retryable: error.options?.retryable ?? declaredRetryable ?? false,
1272
+ retryable: providerErrorOption(error, "retryable") ?? declaredRetryable ?? false,
1237
1273
  };
1238
1274
  }
1239
1275
  // Missing-secret errors carry the canonical credential_unavailable category
@@ -1241,29 +1277,29 @@ function providerObservabilityDetails(
1241
1277
  // the upstream. Matched by code (not constructor) so both the SDK-owned
1242
1278
  // runtime gate and any not-yet-migrated provider-thrown MISSING_SECRET
1243
1279
  // serialize identically, including across duplicate SDK module instances.
1244
- if (isProviderError(error) && error.code === MISSING_SECRET_CODE) {
1280
+ if (isProviderError(error) && providerErrorCode(error) === MISSING_SECRET_CODE) {
1245
1281
  return {
1246
- category: error.options?.category ?? "credential_unavailable",
1282
+ category: providerErrorOption(error, "category") ?? "credential_unavailable",
1247
1283
  taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
1248
- retryable: error.options?.retryable ?? declaredRetryable ?? false,
1284
+ retryable: providerErrorOption(error, "retryable") ?? declaredRetryable ?? false,
1249
1285
  };
1250
1286
  }
1251
1287
  if (!isTransportError(error)) {
1252
1288
  return undefined;
1253
1289
  }
1254
1290
  const isProxyPoolCode =
1255
- error.code === PROXY_POOL_EXHAUSTED_CODE ||
1256
- error.code === PROXY_EDGE_AUTH_REJECTED_CODE ||
1257
- error.code === "PROXY_ALLOCATION_FAILED";
1291
+ providerErrorCode(error) === PROXY_POOL_EXHAUSTED_CODE ||
1292
+ providerErrorCode(error) === PROXY_EDGE_AUTH_REJECTED_CODE ||
1293
+ providerErrorCode(error) === "PROXY_ALLOCATION_FAILED";
1258
1294
  const category =
1259
- error.options?.category ??
1295
+ providerErrorOption(error, "category") ??
1260
1296
  (isProxyPoolCode
1261
1297
  ? "proxy_pool"
1262
- : error.code === PROXY_AUTH_IP_DENIED_CODE
1298
+ : providerErrorCode(error) === PROXY_AUTH_IP_DENIED_CODE
1263
1299
  ? "anti_bot_blocked"
1264
- : error.code === "transport_timeout"
1300
+ : providerErrorCode(error) === "transport_timeout"
1265
1301
  ? "timeout"
1266
- : error.code === "transport_network_error"
1302
+ : providerErrorCode(error) === "transport_network_error"
1267
1303
  ? "network"
1268
1304
  : error.upstreamStatus
1269
1305
  ? categoryForStatus(error.upstreamStatus)
@@ -1272,7 +1308,7 @@ function providerObservabilityDetails(
1272
1308
  category,
1273
1309
  taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
1274
1310
  retryable:
1275
- error.options?.retryable ??
1311
+ providerErrorOption(error, "retryable") ??
1276
1312
  (category === "upstream_http" && error.upstreamStatus
1277
1313
  ? error.upstreamStatus >= 500
1278
1314
  : isRetryableCategory(category)),
@@ -1280,7 +1316,7 @@ function providerObservabilityDetails(
1280
1316
  };
1281
1317
  }
1282
1318
 
1283
- function errorObservabilityDetails(
1319
+ function classifiedErrorObservabilityDetails(
1284
1320
  error: unknown,
1285
1321
  declaredErrorCode?: OperationErrorCode,
1286
1322
  ): ErrorObservabilityDetails {
@@ -1290,19 +1326,19 @@ function errorObservabilityDetails(
1290
1326
 
1291
1327
  if (error instanceof z.ZodError || isValidationError(error)) {
1292
1328
  const declaredStatus = effectiveDeclaration?.status;
1329
+ const providerCategory = providerErrorOption(error, "category");
1293
1330
  return {
1294
- category:
1295
- isProviderError(error) && error.options?.category
1296
- ? error.options.category
1297
- : isEmittableErrorStatus(declaredStatus) &&
1298
- categoryForStatus(declaredStatus) === "upstream_rejected"
1299
- ? "upstream_rejected"
1300
- : isEmittableErrorStatus(declaredStatus) && declaredStatus >= 500
1301
- ? "provider_error"
1302
- : "input_validation",
1331
+ category: providerCategory
1332
+ ? providerCategory
1333
+ : isEmittableErrorStatus(declaredStatus) &&
1334
+ categoryForStatus(declaredStatus) === "upstream_rejected"
1335
+ ? "upstream_rejected"
1336
+ : isEmittableErrorStatus(declaredStatus) && declaredStatus >= 500
1337
+ ? "provider_error"
1338
+ : "input_validation",
1303
1339
  taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
1304
1340
  retryable: isProviderError(error)
1305
- ? (error.options?.retryable ?? effectiveDeclaration?.retryable ?? false)
1341
+ ? (providerErrorOption(error, "retryable") ?? effectiveDeclaration?.retryable ?? false)
1306
1342
  : false,
1307
1343
  };
1308
1344
  }
@@ -1322,15 +1358,16 @@ function errorObservabilityDetails(
1322
1358
  // author set an explicit category.
1323
1359
  const declaredStatus = effectiveDeclaration?.status;
1324
1360
  const rejectionDefault =
1325
- error.code === "UPSTREAM_REJECTED" ||
1361
+ providerErrorCode(error) === "UPSTREAM_REJECTED" ||
1326
1362
  (isEmittableErrorStatus(declaredStatus) &&
1327
1363
  categoryForStatus(declaredStatus) === "upstream_rejected")
1328
1364
  ? ("upstream_rejected" as const)
1329
1365
  : ("provider_error" as const);
1330
1366
  return {
1331
- category: error.options?.category ?? rejectionDefault,
1367
+ category: providerErrorOption(error, "category") ?? rejectionDefault,
1332
1368
  taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
1333
- retryable: error.options?.retryable ?? effectiveDeclaration?.retryable ?? false,
1369
+ retryable:
1370
+ providerErrorOption(error, "retryable") ?? effectiveDeclaration?.retryable ?? false,
1334
1371
  };
1335
1372
  }
1336
1373
 
@@ -1341,16 +1378,24 @@ function errorObservabilityDetails(
1341
1378
  };
1342
1379
  }
1343
1380
 
1344
- function responseWithErrorObservability(
1345
- response: Response,
1381
+ function errorObservabilityDetails(
1346
1382
  error: unknown,
1347
1383
  declaredErrorCode?: OperationErrorCode,
1384
+ ): ErrorObservabilityDetails {
1385
+ const details = classifiedErrorObservabilityDetails(error, declaredErrorCode);
1386
+ const providerObservability = safeProviderErrorObservability(error);
1387
+ return {
1388
+ ...details,
1389
+ ...(providerObservability ? { providerObservability } : {}),
1390
+ };
1391
+ }
1392
+
1393
+ function responseWithErrorObservability(
1394
+ response: Response,
1395
+ observabilityDetails: ErrorObservabilityDetails,
1348
1396
  ): Response {
1349
1397
  const headers = new Headers(response.headers);
1350
- headers.set(
1351
- ERROR_OBSERVABILITY_HEADER,
1352
- JSON.stringify(errorObservabilityDetails(error, declaredErrorCode)),
1353
- );
1398
+ headers.set(ERROR_OBSERVABILITY_HEADER, JSON.stringify(observabilityDetails));
1354
1399
  return new Response(response.body, {
1355
1400
  status: response.status,
1356
1401
  statusText: response.statusText,
@@ -1360,18 +1405,18 @@ function responseWithErrorObservability(
1360
1405
 
1361
1406
  function publicProviderErrorMessage(error: ProviderError): string {
1362
1407
  if (isTransportError(error)) {
1363
- if (error.code === PROXY_AUTH_IP_DENIED_CODE) {
1408
+ if (providerErrorCode(error) === PROXY_AUTH_IP_DENIED_CODE) {
1364
1409
  return error.message;
1365
1410
  }
1366
- if (error.code === PROXY_EDGE_AUTH_REJECTED_CODE) {
1411
+ if (providerErrorCode(error) === PROXY_EDGE_AUTH_REJECTED_CODE) {
1367
1412
  return error.message;
1368
1413
  }
1369
- if (error.code === PROXY_POOL_EXHAUSTED_CODE) {
1414
+ if (providerErrorCode(error) === PROXY_POOL_EXHAUSTED_CODE) {
1370
1415
  return error.message;
1371
1416
  }
1372
- if (error.code === "transport_timeout") return "Request timed out";
1373
- if (error.code === "transport_network_error") return "Network error";
1374
- if (error.code === "upstream_http_error" && error.status) {
1417
+ if (providerErrorCode(error) === "transport_timeout") return "Request timed out";
1418
+ if (providerErrorCode(error) === "transport_network_error") return "Network error";
1419
+ if (providerErrorCode(error) === "upstream_http_error" && error.status) {
1375
1420
  return `Upstream request failed with status ${error.status}`;
1376
1421
  }
1377
1422
  if (error.status) {
@@ -1401,17 +1446,18 @@ function toStatusCode(error: unknown, declaredErrorCode?: OperationErrorCode): P
1401
1446
  }
1402
1447
  // Canonical SDK code → status mapping lives in error-resolution.ts so
1403
1448
  // the authoring lint and this runtime path share one source of truth.
1404
- if (typeof error.code === "string") {
1405
- const mappedStatus = SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES.get(error.code);
1449
+ const code = providerErrorCode(error);
1450
+ if (typeof code === "string") {
1451
+ const mappedStatus = SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES.get(code);
1406
1452
  if (mappedStatus !== undefined) {
1407
1453
  return mappedStatus;
1408
1454
  }
1409
1455
  }
1410
1456
  if (isTransportError(error)) {
1411
- return error.code === "transport_timeout" ? 504 : 502;
1457
+ return providerErrorCode(error) === "transport_timeout" ? 504 : 502;
1412
1458
  }
1413
1459
  if (isValidationError(error)) {
1414
- return error.options?.category === "output_validation" ? 500 : 400;
1460
+ return providerErrorOption(error, "category") === "output_validation" ? 500 : 400;
1415
1461
  }
1416
1462
 
1417
1463
  return 500;
@@ -1425,11 +1471,9 @@ function sdkOwnsErrorResolution(error: unknown): boolean {
1425
1471
  if (isTransportError(error)) return true;
1426
1472
  if (error instanceof z.ZodError) return true;
1427
1473
  if (error instanceof StatefulRoutingDeadlineError) return true;
1428
- return (
1429
- isProviderError(error) &&
1430
- typeof error.code === "string" &&
1431
- SDK_RUNTIME_OWNED_ERROR_CODES.has(error.code)
1432
- );
1474
+ if (!isProviderError(error)) return false;
1475
+ const code = providerErrorCode(error);
1476
+ return typeof code === "string" && SDK_RUNTIME_OWNED_ERROR_CODES.has(code);
1433
1477
  }
1434
1478
 
1435
1479
  type OperationErrorCodeLookup = ReadonlyMap<string, ReadonlyMap<string, OperationErrorCode>>;
@@ -1450,8 +1494,9 @@ function declaredErrorCodeFor(
1450
1494
  operationId: string | undefined,
1451
1495
  lookup: OperationErrorCodeLookup,
1452
1496
  ): OperationErrorCode | undefined {
1453
- if (!operationId || !isProviderError(error) || typeof error.code !== "string") return undefined;
1454
- return lookup.get(operationId)?.get(error.code);
1497
+ const code = providerErrorCode(error);
1498
+ if (!operationId || !code) return undefined;
1499
+ return lookup.get(operationId)?.get(code);
1455
1500
  }
1456
1501
 
1457
1502
  function extractRequestId(raw: unknown): string | undefined {
@@ -1463,14 +1508,24 @@ function extractRequestId(raw: unknown): string | undefined {
1463
1508
  return typeof value === "string" ? value : undefined;
1464
1509
  }
1465
1510
 
1466
- type ProviderErrorCauseFrame = {
1511
+ export type ProviderErrorCauseFrame = {
1467
1512
  errorClass: string;
1468
1513
  code?: string;
1514
+ message: string;
1469
1515
  messageLength: number;
1470
1516
  messageFingerprint: string;
1517
+ providerObservability?: ProviderErrorObservability;
1471
1518
  };
1472
1519
 
1473
1520
  const MAX_PROVIDER_ERROR_CAUSE_FRAMES = 5;
1521
+ const MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH = 300;
1522
+
1523
+ function providerErrorCauseMessage(message: string): string {
1524
+ const sanitized = sanitizeDiagnosticText(message);
1525
+ return sanitized.length > MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH
1526
+ ? `${sanitized.slice(0, MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH)}… [truncated]`
1527
+ : sanitized;
1528
+ }
1474
1529
 
1475
1530
  function providerErrorCauseChain(error: unknown): ProviderErrorCauseFrame[] | undefined {
1476
1531
  if (!(error instanceof Error) && !isProviderError(error)) return undefined;
@@ -1486,11 +1541,15 @@ function providerErrorCauseChain(error: unknown): ProviderErrorCauseFrame[] | un
1486
1541
  ) {
1487
1542
  seen.add(cause);
1488
1543
  const message = cause.message;
1544
+ const providerObservability = safeProviderErrorObservability(cause);
1545
+ const causeCode = providerErrorCode(cause);
1489
1546
  frames.push({
1490
1547
  errorClass: cause.name,
1491
- ...(isProviderError(cause) && typeof cause.code === "string" ? { code: cause.code } : {}),
1548
+ ...(causeCode !== undefined ? { code: causeCode } : {}),
1549
+ message: providerErrorCauseMessage(message),
1492
1550
  messageLength: message.length,
1493
1551
  messageFingerprint: createHash("sha256").update(message).digest("hex").slice(0, 12),
1552
+ ...(providerObservability ? { providerObservability } : {}),
1494
1553
  });
1495
1554
  cause = cause.cause;
1496
1555
  }
@@ -1507,11 +1566,13 @@ function logProviderError(
1507
1566
  error: unknown,
1508
1567
  status: number,
1509
1568
  cost: ProviderRequestCost,
1510
- declaredErrorCode?: OperationErrorCode,
1511
- proxyTelemetry?: ProxyTelemetryCollector,
1569
+ declaredErrorCode: OperationErrorCode | undefined,
1570
+ proxyTelemetry: ProxyTelemetryCollector | undefined,
1571
+ observabilityDetails: ErrorObservabilityDetails,
1512
1572
  ): void {
1573
+ const providerCode = isProviderError(error) ? providerErrorCode(error) : undefined;
1513
1574
  const code = isProviderError(error)
1514
- ? (error.code ?? "provider_error")
1575
+ ? (providerCode ?? "provider_error")
1515
1576
  : error instanceof z.ZodError
1516
1577
  ? "invalid_request"
1517
1578
  : error instanceof StatefulRoutingDeadlineError
@@ -1520,16 +1581,22 @@ function logProviderError(
1520
1581
  const errorClass = error instanceof Error ? error.name : typeof error;
1521
1582
  const message = error instanceof Error ? error.message : String(error);
1522
1583
  const causeChain = providerErrorCauseChain(error);
1523
- const details = errorObservabilityDetails(error, declaredErrorCode);
1584
+ const details = observabilityDetails;
1524
1585
  const isUnregisteredProviderErrorCode =
1525
1586
  status === 500 &&
1526
1587
  isProviderError(error) &&
1527
1588
  !isValidationError(error) &&
1528
- typeof error.code === "string" &&
1529
- !SDK_OWNED_PROVIDER_ERROR_CODES.has(error.code) &&
1589
+ typeof providerCode === "string" &&
1590
+ !SDK_OWNED_PROVIDER_ERROR_CODES.has(providerCode) &&
1530
1591
  declaredErrorCode === undefined;
1531
1592
  const proxy = proxyTelemetry?.toLogPayload();
1532
1593
  const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
1594
+ // The logger is caller-supplied and may mutate the event synchronously.
1595
+ // Give it an independent snapshot so those mutations cannot corrupt the
1596
+ // observability header serialized immediately afterwards.
1597
+ const providerObservability = details.providerObservability
1598
+ ? { ...details.providerObservability }
1599
+ : undefined;
1533
1600
  emit({
1534
1601
  level: status >= 500 ? "error" : "warn",
1535
1602
  event: "provider_request_failed",
@@ -1544,6 +1611,7 @@ function logProviderError(
1544
1611
  errorClass,
1545
1612
  message,
1546
1613
  ...(causeChain ? { causeChain } : {}),
1614
+ ...(providerObservability ? { providerObservability } : {}),
1547
1615
  ...(details.upstreamStatus ? { upstreamStatus: details.upstreamStatus } : {}),
1548
1616
  errorCategory: details.category,
1549
1617
  taxonomyVersion: details.taxonomyVersion,
@@ -2375,7 +2443,11 @@ function createServerAppWithCapabilityModules(
2375
2443
 
2376
2444
  app.notFound((c) => {
2377
2445
  const error = new ProviderError("Not found", { code: "not_found", retryable: false });
2378
- return responseWithErrorObservability(c.json(toErrorResponse(error), 404), error);
2446
+ const observabilityDetails = errorObservabilityDetails(error);
2447
+ return responseWithErrorObservability(
2448
+ c.json(toErrorResponse(error, undefined, observabilityDetails), 404),
2449
+ observabilityDetails,
2450
+ );
2379
2451
  });
2380
2452
 
2381
2453
  app.get("/health", (c) =>
@@ -2534,10 +2606,14 @@ function createServerAppWithCapabilityModules(
2534
2606
  } catch (error) {
2535
2607
  const declaredErrorCode = declaredErrorCodeFor(error, operationId, operationErrorCodes);
2536
2608
  const status = toStatusCode(error, declaredErrorCode);
2537
- if (isProviderError(error) && error.code === "STATEFUL_FORWARDING_REPLAY_CACHE_FULL") {
2609
+ if (
2610
+ isProviderError(error) &&
2611
+ providerErrorCode(error) === "STATEFUL_FORWARDING_REPLAY_CACHE_FULL"
2612
+ ) {
2538
2613
  c.header("Retry-After", String(STATEFUL_FORWARDING_REPLAY_RETRY_AFTER_SECONDS));
2539
2614
  }
2540
2615
  const requestId = extractRequestId(rawBody);
2616
+ const observabilityDetails = errorObservabilityDetails(error, declaredErrorCode);
2541
2617
  logProviderError(
2542
2618
  logger,
2543
2619
  provider,
@@ -2548,11 +2624,12 @@ function createServerAppWithCapabilityModules(
2548
2624
  status,
2549
2625
  finishRequestCost(requestCost),
2550
2626
  declaredErrorCode,
2627
+ undefined,
2628
+ observabilityDetails,
2551
2629
  );
2552
2630
  return responseWithErrorObservability(
2553
- c.json(toErrorResponse(error, requestId, declaredErrorCode), status),
2554
- error,
2555
- declaredErrorCode,
2631
+ c.json(toErrorResponse(error, requestId, observabilityDetails), status),
2632
+ observabilityDetails,
2556
2633
  );
2557
2634
  }
2558
2635
  });
@@ -2609,6 +2686,7 @@ function createServerAppWithCapabilityModules(
2609
2686
  const declaredErrorCode = declaredErrorCodeFor(error, operation, operationErrorCodes);
2610
2687
  const status = toStatusCode(error, declaredErrorCode);
2611
2688
  const requestId = extractRequestId(rawBody);
2689
+ const observabilityDetails = errorObservabilityDetails(error, declaredErrorCode);
2612
2690
  logProviderError(
2613
2691
  logger,
2614
2692
  provider,
@@ -2620,13 +2698,13 @@ function createServerAppWithCapabilityModules(
2620
2698
  finishRequestCost(requestCost),
2621
2699
  declaredErrorCode,
2622
2700
  proxyTelemetry,
2701
+ observabilityDetails,
2623
2702
  );
2624
2703
  const telemetryHeader = proxyTelemetry.toHeaderValue();
2625
2704
  if (telemetryHeader) c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
2626
2705
  return responseWithErrorObservability(
2627
- c.json(toErrorResponse(error, requestId, declaredErrorCode), status),
2628
- error,
2629
- declaredErrorCode,
2706
+ c.json(toErrorResponse(error, requestId, observabilityDetails), status),
2707
+ observabilityDetails,
2630
2708
  );
2631
2709
  }
2632
2710
  });
@@ -2668,6 +2746,7 @@ function createServerAppWithCapabilityModules(
2668
2746
  } catch (error) {
2669
2747
  const status = toStatusCode(error);
2670
2748
  const requestId = extractRequestId(rawBody);
2749
+ const observabilityDetails = errorObservabilityDetails(error);
2671
2750
  logProviderError(
2672
2751
  logger,
2673
2752
  provider,
@@ -2679,12 +2758,13 @@ function createServerAppWithCapabilityModules(
2679
2758
  finishRequestCost(requestCost),
2680
2759
  undefined,
2681
2760
  proxyTelemetry,
2761
+ observabilityDetails,
2682
2762
  );
2683
2763
  const telemetryHeader = proxyTelemetry.toHeaderValue();
2684
2764
  if (telemetryHeader) c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
2685
2765
  return responseWithErrorObservability(
2686
- c.json(toErrorResponse(error, requestId), status),
2687
- error,
2766
+ c.json(toErrorResponse(error, requestId, observabilityDetails), status),
2767
+ observabilityDetails,
2688
2768
  );
2689
2769
  }
2690
2770
  });
@@ -2726,6 +2806,7 @@ function createServerAppWithCapabilityModules(
2726
2806
  } catch (error) {
2727
2807
  const status = toStatusCode(error);
2728
2808
  const requestId = extractRequestId(rawBody);
2809
+ const observabilityDetails = errorObservabilityDetails(error);
2729
2810
  logProviderError(
2730
2811
  logger,
2731
2812
  provider,
@@ -2737,12 +2818,13 @@ function createServerAppWithCapabilityModules(
2737
2818
  finishRequestCost(requestCost),
2738
2819
  undefined,
2739
2820
  proxyTelemetry,
2821
+ observabilityDetails,
2740
2822
  );
2741
2823
  const telemetryHeader = proxyTelemetry.toHeaderValue();
2742
2824
  if (telemetryHeader) c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
2743
2825
  return responseWithErrorObservability(
2744
- c.json(toErrorResponse(error, requestId), status),
2745
- error,
2826
+ c.json(toErrorResponse(error, requestId, observabilityDetails), status),
2827
+ observabilityDetails,
2746
2828
  );
2747
2829
  }
2748
2830
  });
@@ -2784,6 +2866,7 @@ function createServerAppWithCapabilityModules(
2784
2866
  } catch (error) {
2785
2867
  const status = toStatusCode(error);
2786
2868
  const requestId = extractRequestId(rawBody);
2869
+ const observabilityDetails = errorObservabilityDetails(error);
2787
2870
  logProviderError(
2788
2871
  logger,
2789
2872
  provider,
@@ -2795,12 +2878,13 @@ function createServerAppWithCapabilityModules(
2795
2878
  finishRequestCost(requestCost),
2796
2879
  undefined,
2797
2880
  proxyTelemetry,
2881
+ observabilityDetails,
2798
2882
  );
2799
2883
  const telemetryHeader = proxyTelemetry.toHeaderValue();
2800
2884
  if (telemetryHeader) c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
2801
2885
  return responseWithErrorObservability(
2802
- c.json(toErrorResponse(error, requestId), status),
2803
- error,
2886
+ c.json(toErrorResponse(error, requestId, observabilityDetails), status),
2887
+ observabilityDetails,
2804
2888
  );
2805
2889
  }
2806
2890
  });
@@ -2842,6 +2926,7 @@ function createServerAppWithCapabilityModules(
2842
2926
  } catch (error) {
2843
2927
  const status = toStatusCode(error);
2844
2928
  const requestId = extractRequestId(rawBody);
2929
+ const observabilityDetails = errorObservabilityDetails(error);
2845
2930
  logProviderError(
2846
2931
  logger,
2847
2932
  provider,
@@ -2853,12 +2938,13 @@ function createServerAppWithCapabilityModules(
2853
2938
  finishRequestCost(requestCost),
2854
2939
  undefined,
2855
2940
  proxyTelemetry,
2941
+ observabilityDetails,
2856
2942
  );
2857
2943
  const telemetryHeader = proxyTelemetry.toHeaderValue();
2858
2944
  if (telemetryHeader) c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
2859
2945
  return responseWithErrorObservability(
2860
- c.json(toErrorResponse(error, requestId), status),
2861
- error,
2946
+ c.json(toErrorResponse(error, requestId, observabilityDetails), status),
2947
+ observabilityDetails,
2862
2948
  );
2863
2949
  }
2864
2950
  });
@@ -2900,6 +2986,7 @@ function createServerAppWithCapabilityModules(
2900
2986
  } catch (error) {
2901
2987
  const status = toStatusCode(error);
2902
2988
  const requestId = extractRequestId(rawBody);
2989
+ const observabilityDetails = errorObservabilityDetails(error);
2903
2990
  logProviderError(
2904
2991
  logger,
2905
2992
  provider,
@@ -2911,12 +2998,13 @@ function createServerAppWithCapabilityModules(
2911
2998
  finishRequestCost(requestCost),
2912
2999
  undefined,
2913
3000
  proxyTelemetry,
3001
+ observabilityDetails,
2914
3002
  );
2915
3003
  const telemetryHeader = proxyTelemetry.toHeaderValue();
2916
3004
  if (telemetryHeader) c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
2917
3005
  return responseWithErrorObservability(
2918
- c.json(toErrorResponse(error, requestId), status),
2919
- error,
3006
+ c.json(toErrorResponse(error, requestId, observabilityDetails), status),
3007
+ observabilityDetails,
2920
3008
  );
2921
3009
  }
2922
3010
  });