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

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 (47) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/bin/apifuse-check.ts +61 -0
  3. package/bin/apifuse-migrate-shape.ts +202 -0
  4. package/bin/apifuse-submit-check.ts +1773 -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-operation-shape.d.ts +44 -0
  9. package/dist/cli/migrate-operation-shape.js +113 -0
  10. package/dist/cli/migrate-provider-shape.d.ts +52 -0
  11. package/dist/cli/migrate-provider-shape.js +578 -0
  12. package/dist/cli/templates/provider/provider.json.tpl +6 -0
  13. package/dist/contract.js +1 -0
  14. package/dist/define.js +22 -1
  15. package/dist/error-observability.d.ts +7 -0
  16. package/dist/error-observability.js +61 -0
  17. package/dist/errors.d.ts +15 -0
  18. package/dist/fixture-sanitization.js +13 -3
  19. package/dist/index.d.ts +1 -1
  20. package/dist/provider.d.ts +1 -1
  21. package/dist/runtime/executor.js +11 -1
  22. package/dist/server/error-observability.d.ts +1 -0
  23. package/dist/server/error-observability.js +1 -0
  24. package/dist/server/index.d.ts +2 -1
  25. package/dist/server/self-test.js +3 -0
  26. package/dist/server/serve-implementation.d.ts +12 -0
  27. package/dist/server/serve-implementation.js +174 -66
  28. package/dist/types.d.ts +18 -10
  29. package/package.json +1 -1
  30. package/src/cli/commands.ts +10 -0
  31. package/src/cli/create.ts +6 -0
  32. package/src/cli/migrate-operation-shape.ts +184 -0
  33. package/src/cli/migrate-provider-shape.ts +772 -0
  34. package/src/cli/templates/provider/provider.json.tpl +6 -0
  35. package/src/contract.ts +1 -0
  36. package/src/define.ts +33 -1
  37. package/src/error-observability.ts +64 -0
  38. package/src/errors.ts +16 -0
  39. package/src/fixture-sanitization.ts +19 -3
  40. package/src/index.ts +1 -0
  41. package/src/provider.ts +1 -0
  42. package/src/runtime/executor.ts +13 -1
  43. package/src/server/error-observability.ts +1 -0
  44. package/src/server/index.ts +2 -0
  45. package/src/server/self-test.ts +5 -0
  46. package/src/server/serve-implementation.ts +214 -84
  47. 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,13 @@ import {
19
20
  isTransportError,
20
21
  isValidationError,
21
22
  ProviderError,
23
+ type ProviderErrorObservability,
24
+ type ProviderErrorOptions,
22
25
  } from "../errors.js";
26
+ import {
27
+ REDACTED_FIXTURE_VALUE,
28
+ sanitizeDiagnosticText,
29
+ } from "../fixture-sanitization.js";
23
30
  import {
24
31
  loadProviderLocaleCatalogs,
25
32
  localizeAuthTurn,
@@ -140,7 +147,36 @@ export type ErrorObservabilityDetails = {
140
147
  taxonomyVersion: string;
141
148
  retryable: boolean;
142
149
  upstreamStatus?: number;
150
+ providerObservability?: ProviderErrorObservability;
143
151
  };
152
+
153
+ // Provider errors normally expose `options` through an own data property, but
154
+ // providers can replace that property with a throwing accessor. Provider-controlled
155
+ // accessor failures are absorbed into canonical classification rather than
156
+ // propagated. By contract, read `options` directly instead of the public
157
+ // `fix`/`details` convenience getters; provider-controlled accessors are not trusted.
158
+ function providerErrorOption<K extends keyof ProviderErrorOptions>(
159
+ error: unknown,
160
+ key: K,
161
+ ): ProviderErrorOptions[K] | undefined {
162
+ if (!isProviderError(error)) return undefined;
163
+ try {
164
+ return (error as ProviderError).options?.[key] as ProviderErrorOptions[K] | undefined;
165
+ } catch {
166
+ return undefined;
167
+ }
168
+ }
169
+
170
+ function providerErrorCode(error: unknown): string | undefined {
171
+ if (!isProviderError(error)) return undefined;
172
+ try {
173
+ const code: unknown = (error as ProviderError).code;
174
+ return typeof code === "string" ? code : undefined;
175
+ } catch {
176
+ return undefined;
177
+ }
178
+ }
179
+
144
180
  const AUTH_FLOW_LOCALES = ["en", "ko", "ja"] as const;
145
181
  const retryResponseMeta = new WeakMap<ProviderContext, HttpRetrySummary>();
146
182
  const STATEFUL_INTERNAL_OPERATIONS_ROUTE = "/__apifuse/stateful/operations";
@@ -989,6 +1025,8 @@ export type ProviderServerLogEvent =
989
1025
  errorCategory?: ProviderErrorCategory;
990
1026
  taxonomyVersion?: string;
991
1027
  retryable?: boolean;
1028
+ providerObservability?: ProviderErrorObservability;
1029
+ causeChain?: ProviderErrorCauseFrame[];
992
1030
  signal?: "unregistered_provider_error_code";
993
1031
  signalFix?: string;
994
1032
  issues?: Array<{ path: string; code: string; message: string }>;
@@ -1135,8 +1173,9 @@ function zodDetails(error: z.ZodError): Array<{
1135
1173
  function publicErrorSource(error: unknown, category: ProviderErrorCategory): ProviderErrorSource {
1136
1174
  if (error instanceof StatefulRoutingDeadlineError) return "apifuse";
1137
1175
  if (isProviderError(error)) {
1138
- if (error.code === MISSING_SECRET_CODE) return "apifuse";
1139
- if (error.code === "UPSTREAM_ERROR" || error.code === "BLOCKED") {
1176
+ const code = providerErrorCode(error);
1177
+ if (code === MISSING_SECRET_CODE) return "apifuse";
1178
+ if (code === "UPSTREAM_ERROR" || code === "BLOCKED") {
1140
1179
  return "upstream_failure";
1141
1180
  }
1142
1181
  }
@@ -1145,10 +1184,10 @@ function publicErrorSource(error: unknown, category: ProviderErrorCategory): Pro
1145
1184
 
1146
1185
  function toErrorResponse(
1147
1186
  error: unknown,
1148
- requestId?: string,
1149
- declaredErrorCode?: OperationErrorCode,
1187
+ requestId: string | undefined,
1188
+ observabilityDetails: ErrorObservabilityDetails,
1150
1189
  ): OperationErrorResponse {
1151
- const observability = errorObservabilityDetails(error, declaredErrorCode);
1190
+ const observability = observabilityDetails;
1152
1191
  const source = publicErrorSource(error, observability.category);
1153
1192
  if (error instanceof StatefulRoutingDeadlineError) {
1154
1193
  return {
@@ -1163,15 +1202,15 @@ function toErrorResponse(
1163
1202
  }
1164
1203
 
1165
1204
  if (isProviderError(error)) {
1166
- const details = error.details;
1205
+ const details = providerErrorOption(error, "details");
1167
1206
  return {
1168
1207
  error: {
1169
- code: error.code ?? "provider_error",
1208
+ code: providerErrorCode(error) ?? "provider_error",
1170
1209
  message: publicProviderErrorMessage(error),
1171
1210
  ...(requestId ? { requestId } : {}),
1172
1211
  retryable: observability.retryable,
1173
1212
  source,
1174
- ...(error.fix ? { fix: error.fix } : {}),
1213
+ ...(providerErrorOption(error, "fix") ? { fix: providerErrorOption(error, "fix") } : {}),
1175
1214
  ...(details !== undefined ? { details } : {}),
1176
1215
  },
1177
1216
  };
@@ -1231,9 +1270,9 @@ function providerObservabilityDetails(
1231
1270
  // signal for exactly the retryOnAuthRefresh operations it is meant to enable.
1232
1271
  if (isSessionExpiredError(error)) {
1233
1272
  return {
1234
- category: error.options?.category ?? "credential_expired",
1273
+ category: providerErrorOption(error, "category") ?? "credential_expired",
1235
1274
  taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
1236
- retryable: error.options?.retryable ?? declaredRetryable ?? false,
1275
+ retryable: providerErrorOption(error, "retryable") ?? declaredRetryable ?? false,
1237
1276
  };
1238
1277
  }
1239
1278
  // Missing-secret errors carry the canonical credential_unavailable category
@@ -1241,29 +1280,29 @@ function providerObservabilityDetails(
1241
1280
  // the upstream. Matched by code (not constructor) so both the SDK-owned
1242
1281
  // runtime gate and any not-yet-migrated provider-thrown MISSING_SECRET
1243
1282
  // serialize identically, including across duplicate SDK module instances.
1244
- if (isProviderError(error) && error.code === MISSING_SECRET_CODE) {
1283
+ if (isProviderError(error) && providerErrorCode(error) === MISSING_SECRET_CODE) {
1245
1284
  return {
1246
- category: error.options?.category ?? "credential_unavailable",
1285
+ category: providerErrorOption(error, "category") ?? "credential_unavailable",
1247
1286
  taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
1248
- retryable: error.options?.retryable ?? declaredRetryable ?? false,
1287
+ retryable: providerErrorOption(error, "retryable") ?? declaredRetryable ?? false,
1249
1288
  };
1250
1289
  }
1251
1290
  if (!isTransportError(error)) {
1252
1291
  return undefined;
1253
1292
  }
1254
1293
  const isProxyPoolCode =
1255
- error.code === PROXY_POOL_EXHAUSTED_CODE ||
1256
- error.code === PROXY_EDGE_AUTH_REJECTED_CODE ||
1257
- error.code === "PROXY_ALLOCATION_FAILED";
1294
+ providerErrorCode(error) === PROXY_POOL_EXHAUSTED_CODE ||
1295
+ providerErrorCode(error) === PROXY_EDGE_AUTH_REJECTED_CODE ||
1296
+ providerErrorCode(error) === "PROXY_ALLOCATION_FAILED";
1258
1297
  const category =
1259
- error.options?.category ??
1298
+ providerErrorOption(error, "category") ??
1260
1299
  (isProxyPoolCode
1261
1300
  ? "proxy_pool"
1262
- : error.code === PROXY_AUTH_IP_DENIED_CODE
1301
+ : providerErrorCode(error) === PROXY_AUTH_IP_DENIED_CODE
1263
1302
  ? "anti_bot_blocked"
1264
- : error.code === "transport_timeout"
1303
+ : providerErrorCode(error) === "transport_timeout"
1265
1304
  ? "timeout"
1266
- : error.code === "transport_network_error"
1305
+ : providerErrorCode(error) === "transport_network_error"
1267
1306
  ? "network"
1268
1307
  : error.upstreamStatus
1269
1308
  ? categoryForStatus(error.upstreamStatus)
@@ -1272,7 +1311,7 @@ function providerObservabilityDetails(
1272
1311
  category,
1273
1312
  taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
1274
1313
  retryable:
1275
- error.options?.retryable ??
1314
+ providerErrorOption(error, "retryable") ??
1276
1315
  (category === "upstream_http" && error.upstreamStatus
1277
1316
  ? error.upstreamStatus >= 500
1278
1317
  : isRetryableCategory(category)),
@@ -1280,7 +1319,7 @@ function providerObservabilityDetails(
1280
1319
  };
1281
1320
  }
1282
1321
 
1283
- function errorObservabilityDetails(
1322
+ function classifiedErrorObservabilityDetails(
1284
1323
  error: unknown,
1285
1324
  declaredErrorCode?: OperationErrorCode,
1286
1325
  ): ErrorObservabilityDetails {
@@ -1290,19 +1329,19 @@ function errorObservabilityDetails(
1290
1329
 
1291
1330
  if (error instanceof z.ZodError || isValidationError(error)) {
1292
1331
  const declaredStatus = effectiveDeclaration?.status;
1332
+ const providerCategory = providerErrorOption(error, "category");
1293
1333
  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",
1334
+ category: providerCategory
1335
+ ? providerCategory
1336
+ : isEmittableErrorStatus(declaredStatus) &&
1337
+ categoryForStatus(declaredStatus) === "upstream_rejected"
1338
+ ? "upstream_rejected"
1339
+ : isEmittableErrorStatus(declaredStatus) && declaredStatus >= 500
1340
+ ? "provider_error"
1341
+ : "input_validation",
1303
1342
  taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
1304
1343
  retryable: isProviderError(error)
1305
- ? (error.options?.retryable ?? effectiveDeclaration?.retryable ?? false)
1344
+ ? (providerErrorOption(error, "retryable") ?? effectiveDeclaration?.retryable ?? false)
1306
1345
  : false,
1307
1346
  };
1308
1347
  }
@@ -1322,15 +1361,16 @@ function errorObservabilityDetails(
1322
1361
  // author set an explicit category.
1323
1362
  const declaredStatus = effectiveDeclaration?.status;
1324
1363
  const rejectionDefault =
1325
- error.code === "UPSTREAM_REJECTED" ||
1364
+ providerErrorCode(error) === "UPSTREAM_REJECTED" ||
1326
1365
  (isEmittableErrorStatus(declaredStatus) &&
1327
1366
  categoryForStatus(declaredStatus) === "upstream_rejected")
1328
1367
  ? ("upstream_rejected" as const)
1329
1368
  : ("provider_error" as const);
1330
1369
  return {
1331
- category: error.options?.category ?? rejectionDefault,
1370
+ category: providerErrorOption(error, "category") ?? rejectionDefault,
1332
1371
  taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
1333
- retryable: error.options?.retryable ?? effectiveDeclaration?.retryable ?? false,
1372
+ retryable:
1373
+ providerErrorOption(error, "retryable") ?? effectiveDeclaration?.retryable ?? false,
1334
1374
  };
1335
1375
  }
1336
1376
 
@@ -1341,16 +1381,24 @@ function errorObservabilityDetails(
1341
1381
  };
1342
1382
  }
1343
1383
 
1344
- function responseWithErrorObservability(
1345
- response: Response,
1384
+ function errorObservabilityDetails(
1346
1385
  error: unknown,
1347
1386
  declaredErrorCode?: OperationErrorCode,
1387
+ ): ErrorObservabilityDetails {
1388
+ const details = classifiedErrorObservabilityDetails(error, declaredErrorCode);
1389
+ const providerObservability = safeProviderErrorObservability(error);
1390
+ return {
1391
+ ...details,
1392
+ ...(providerObservability ? { providerObservability } : {}),
1393
+ };
1394
+ }
1395
+
1396
+ function responseWithErrorObservability(
1397
+ response: Response,
1398
+ observabilityDetails: ErrorObservabilityDetails,
1348
1399
  ): Response {
1349
1400
  const headers = new Headers(response.headers);
1350
- headers.set(
1351
- ERROR_OBSERVABILITY_HEADER,
1352
- JSON.stringify(errorObservabilityDetails(error, declaredErrorCode)),
1353
- );
1401
+ headers.set(ERROR_OBSERVABILITY_HEADER, JSON.stringify(observabilityDetails));
1354
1402
  return new Response(response.body, {
1355
1403
  status: response.status,
1356
1404
  statusText: response.statusText,
@@ -1360,18 +1408,18 @@ function responseWithErrorObservability(
1360
1408
 
1361
1409
  function publicProviderErrorMessage(error: ProviderError): string {
1362
1410
  if (isTransportError(error)) {
1363
- if (error.code === PROXY_AUTH_IP_DENIED_CODE) {
1411
+ if (providerErrorCode(error) === PROXY_AUTH_IP_DENIED_CODE) {
1364
1412
  return error.message;
1365
1413
  }
1366
- if (error.code === PROXY_EDGE_AUTH_REJECTED_CODE) {
1414
+ if (providerErrorCode(error) === PROXY_EDGE_AUTH_REJECTED_CODE) {
1367
1415
  return error.message;
1368
1416
  }
1369
- if (error.code === PROXY_POOL_EXHAUSTED_CODE) {
1417
+ if (providerErrorCode(error) === PROXY_POOL_EXHAUSTED_CODE) {
1370
1418
  return error.message;
1371
1419
  }
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) {
1420
+ if (providerErrorCode(error) === "transport_timeout") return "Request timed out";
1421
+ if (providerErrorCode(error) === "transport_network_error") return "Network error";
1422
+ if (providerErrorCode(error) === "upstream_http_error" && error.status) {
1375
1423
  return `Upstream request failed with status ${error.status}`;
1376
1424
  }
1377
1425
  if (error.status) {
@@ -1401,17 +1449,18 @@ function toStatusCode(error: unknown, declaredErrorCode?: OperationErrorCode): P
1401
1449
  }
1402
1450
  // Canonical SDK code → status mapping lives in error-resolution.ts so
1403
1451
  // 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);
1452
+ const code = providerErrorCode(error);
1453
+ if (typeof code === "string") {
1454
+ const mappedStatus = SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES.get(code);
1406
1455
  if (mappedStatus !== undefined) {
1407
1456
  return mappedStatus;
1408
1457
  }
1409
1458
  }
1410
1459
  if (isTransportError(error)) {
1411
- return error.code === "transport_timeout" ? 504 : 502;
1460
+ return providerErrorCode(error) === "transport_timeout" ? 504 : 502;
1412
1461
  }
1413
1462
  if (isValidationError(error)) {
1414
- return error.options?.category === "output_validation" ? 500 : 400;
1463
+ return providerErrorOption(error, "category") === "output_validation" ? 500 : 400;
1415
1464
  }
1416
1465
 
1417
1466
  return 500;
@@ -1425,11 +1474,9 @@ function sdkOwnsErrorResolution(error: unknown): boolean {
1425
1474
  if (isTransportError(error)) return true;
1426
1475
  if (error instanceof z.ZodError) return true;
1427
1476
  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
- );
1477
+ if (!isProviderError(error)) return false;
1478
+ const code = providerErrorCode(error);
1479
+ return typeof code === "string" && SDK_RUNTIME_OWNED_ERROR_CODES.has(code);
1433
1480
  }
1434
1481
 
1435
1482
  type OperationErrorCodeLookup = ReadonlyMap<string, ReadonlyMap<string, OperationErrorCode>>;
@@ -1450,8 +1497,9 @@ function declaredErrorCodeFor(
1450
1497
  operationId: string | undefined,
1451
1498
  lookup: OperationErrorCodeLookup,
1452
1499
  ): OperationErrorCode | undefined {
1453
- if (!operationId || !isProviderError(error) || typeof error.code !== "string") return undefined;
1454
- return lookup.get(operationId)?.get(error.code);
1500
+ const code = providerErrorCode(error);
1501
+ if (!operationId || !code) return undefined;
1502
+ return lookup.get(operationId)?.get(code);
1455
1503
  }
1456
1504
 
1457
1505
  function extractRequestId(raw: unknown): string | undefined {
@@ -1463,14 +1511,63 @@ function extractRequestId(raw: unknown): string | undefined {
1463
1511
  return typeof value === "string" ? value : undefined;
1464
1512
  }
1465
1513
 
1466
- type ProviderErrorCauseFrame = {
1514
+ export type ProviderErrorCauseFrame = {
1467
1515
  errorClass: string;
1468
1516
  code?: string;
1517
+ message: string;
1469
1518
  messageLength: number;
1470
1519
  messageFingerprint: string;
1520
+ providerObservability?: ProviderErrorObservability;
1471
1521
  };
1472
1522
 
1473
1523
  const MAX_PROVIDER_ERROR_CAUSE_FRAMES = 5;
1524
+ const MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH = 300;
1525
+ const UNSTRUCTURED_PROVIDER_ERROR_CAUSE_MESSAGE = "[UNSTRUCTURED_UPSTREAM_TEXT]";
1526
+ const PROVIDER_ERROR_CAUSE_RETAINED_URL_RUN = /https?:\/\/[^\s"'<>]+/giu;
1527
+ const PROVIDER_ERROR_CAUSE_TOKEN_RUN = /\S+/gu;
1528
+ const PROVIDER_ERROR_CAUSE_TOKEN_EDGE_PUNCTUATION =
1529
+ /^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu;
1530
+ const STRUCTURALLY_SAFE_PROVIDER_ERROR_CAUSE_WORDS = new Set([
1531
+ "completion",
1532
+ "diagnostic",
1533
+ "provider",
1534
+ "rejected",
1535
+ "returned",
1536
+ "upstream",
1537
+ ]);
1538
+
1539
+ /**
1540
+ * Cause frames fail closed when sanitization leaves a plausible credential-shaped free-text run.
1541
+ * Redaction sentinels and retained URLs are ignored. Every other whitespace token (or each side
1542
+ * of a structured key=value token) with at least eight Unicode characters must reduce to this
1543
+ * small vocabulary drawn from SDK diagnostics; counting punctuation keeps bare passwords opaque.
1544
+ */
1545
+ function isStructurallySafeProviderErrorCauseMessage(message: string): boolean {
1546
+ const classifiableMessage = message
1547
+ .replaceAll(REDACTED_FIXTURE_VALUE, " ")
1548
+ .replace(PROVIDER_ERROR_CAUSE_RETAINED_URL_RUN, " ");
1549
+ for (const match of classifiableMessage.matchAll(PROVIDER_ERROR_CAUSE_TOKEN_RUN)) {
1550
+ const token = match[0];
1551
+ for (const run of token.split("=")) {
1552
+ const diagnosticWord = run
1553
+ .replace(PROVIDER_ERROR_CAUSE_TOKEN_EDGE_PUNCTUATION, "")
1554
+ .toLowerCase();
1555
+ if (STRUCTURALLY_SAFE_PROVIDER_ERROR_CAUSE_WORDS.has(diagnosticWord)) continue;
1556
+ if ([...run].length >= 8) return false;
1557
+ }
1558
+ }
1559
+ return true;
1560
+ }
1561
+
1562
+ function providerErrorCauseMessage(message: string): string {
1563
+ const sanitized = sanitizeDiagnosticText(message);
1564
+ if (!isStructurallySafeProviderErrorCauseMessage(sanitized)) {
1565
+ return UNSTRUCTURED_PROVIDER_ERROR_CAUSE_MESSAGE;
1566
+ }
1567
+ return sanitized.length > MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH
1568
+ ? `${sanitized.slice(0, MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH)}… [truncated]`
1569
+ : sanitized;
1570
+ }
1474
1571
 
1475
1572
  function providerErrorCauseChain(error: unknown): ProviderErrorCauseFrame[] | undefined {
1476
1573
  if (!(error instanceof Error) && !isProviderError(error)) return undefined;
@@ -1486,11 +1583,15 @@ function providerErrorCauseChain(error: unknown): ProviderErrorCauseFrame[] | un
1486
1583
  ) {
1487
1584
  seen.add(cause);
1488
1585
  const message = cause.message;
1586
+ const providerObservability = safeProviderErrorObservability(cause);
1587
+ const causeCode = providerErrorCode(cause);
1489
1588
  frames.push({
1490
1589
  errorClass: cause.name,
1491
- ...(isProviderError(cause) && typeof cause.code === "string" ? { code: cause.code } : {}),
1590
+ ...(causeCode !== undefined ? { code: causeCode } : {}),
1591
+ message: providerErrorCauseMessage(message),
1492
1592
  messageLength: message.length,
1493
1593
  messageFingerprint: createHash("sha256").update(message).digest("hex").slice(0, 12),
1594
+ ...(providerObservability ? { providerObservability } : {}),
1494
1595
  });
1495
1596
  cause = cause.cause;
1496
1597
  }
@@ -1507,11 +1608,13 @@ function logProviderError(
1507
1608
  error: unknown,
1508
1609
  status: number,
1509
1610
  cost: ProviderRequestCost,
1510
- declaredErrorCode?: OperationErrorCode,
1511
- proxyTelemetry?: ProxyTelemetryCollector,
1611
+ declaredErrorCode: OperationErrorCode | undefined,
1612
+ proxyTelemetry: ProxyTelemetryCollector | undefined,
1613
+ observabilityDetails: ErrorObservabilityDetails,
1512
1614
  ): void {
1615
+ const providerCode = isProviderError(error) ? providerErrorCode(error) : undefined;
1513
1616
  const code = isProviderError(error)
1514
- ? (error.code ?? "provider_error")
1617
+ ? (providerCode ?? "provider_error")
1515
1618
  : error instanceof z.ZodError
1516
1619
  ? "invalid_request"
1517
1620
  : error instanceof StatefulRoutingDeadlineError
@@ -1520,16 +1623,22 @@ function logProviderError(
1520
1623
  const errorClass = error instanceof Error ? error.name : typeof error;
1521
1624
  const message = error instanceof Error ? error.message : String(error);
1522
1625
  const causeChain = providerErrorCauseChain(error);
1523
- const details = errorObservabilityDetails(error, declaredErrorCode);
1626
+ const details = observabilityDetails;
1524
1627
  const isUnregisteredProviderErrorCode =
1525
1628
  status === 500 &&
1526
1629
  isProviderError(error) &&
1527
1630
  !isValidationError(error) &&
1528
- typeof error.code === "string" &&
1529
- !SDK_OWNED_PROVIDER_ERROR_CODES.has(error.code) &&
1631
+ typeof providerCode === "string" &&
1632
+ !SDK_OWNED_PROVIDER_ERROR_CODES.has(providerCode) &&
1530
1633
  declaredErrorCode === undefined;
1531
1634
  const proxy = proxyTelemetry?.toLogPayload();
1532
1635
  const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
1636
+ // The logger is caller-supplied and may mutate the event synchronously.
1637
+ // Give it an independent snapshot so those mutations cannot corrupt the
1638
+ // observability header serialized immediately afterwards.
1639
+ const providerObservability = details.providerObservability
1640
+ ? { ...details.providerObservability }
1641
+ : undefined;
1533
1642
  emit({
1534
1643
  level: status >= 500 ? "error" : "warn",
1535
1644
  event: "provider_request_failed",
@@ -1544,6 +1653,7 @@ function logProviderError(
1544
1653
  errorClass,
1545
1654
  message,
1546
1655
  ...(causeChain ? { causeChain } : {}),
1656
+ ...(providerObservability ? { providerObservability } : {}),
1547
1657
  ...(details.upstreamStatus ? { upstreamStatus: details.upstreamStatus } : {}),
1548
1658
  errorCategory: details.category,
1549
1659
  taxonomyVersion: details.taxonomyVersion,
@@ -2375,7 +2485,11 @@ function createServerAppWithCapabilityModules(
2375
2485
 
2376
2486
  app.notFound((c) => {
2377
2487
  const error = new ProviderError("Not found", { code: "not_found", retryable: false });
2378
- return responseWithErrorObservability(c.json(toErrorResponse(error), 404), error);
2488
+ const observabilityDetails = errorObservabilityDetails(error);
2489
+ return responseWithErrorObservability(
2490
+ c.json(toErrorResponse(error, undefined, observabilityDetails), 404),
2491
+ observabilityDetails,
2492
+ );
2379
2493
  });
2380
2494
 
2381
2495
  app.get("/health", (c) =>
@@ -2534,10 +2648,14 @@ function createServerAppWithCapabilityModules(
2534
2648
  } catch (error) {
2535
2649
  const declaredErrorCode = declaredErrorCodeFor(error, operationId, operationErrorCodes);
2536
2650
  const status = toStatusCode(error, declaredErrorCode);
2537
- if (isProviderError(error) && error.code === "STATEFUL_FORWARDING_REPLAY_CACHE_FULL") {
2651
+ if (
2652
+ isProviderError(error) &&
2653
+ providerErrorCode(error) === "STATEFUL_FORWARDING_REPLAY_CACHE_FULL"
2654
+ ) {
2538
2655
  c.header("Retry-After", String(STATEFUL_FORWARDING_REPLAY_RETRY_AFTER_SECONDS));
2539
2656
  }
2540
2657
  const requestId = extractRequestId(rawBody);
2658
+ const observabilityDetails = errorObservabilityDetails(error, declaredErrorCode);
2541
2659
  logProviderError(
2542
2660
  logger,
2543
2661
  provider,
@@ -2548,11 +2666,12 @@ function createServerAppWithCapabilityModules(
2548
2666
  status,
2549
2667
  finishRequestCost(requestCost),
2550
2668
  declaredErrorCode,
2669
+ undefined,
2670
+ observabilityDetails,
2551
2671
  );
2552
2672
  return responseWithErrorObservability(
2553
- c.json(toErrorResponse(error, requestId, declaredErrorCode), status),
2554
- error,
2555
- declaredErrorCode,
2673
+ c.json(toErrorResponse(error, requestId, observabilityDetails), status),
2674
+ observabilityDetails,
2556
2675
  );
2557
2676
  }
2558
2677
  });
@@ -2609,6 +2728,7 @@ function createServerAppWithCapabilityModules(
2609
2728
  const declaredErrorCode = declaredErrorCodeFor(error, operation, operationErrorCodes);
2610
2729
  const status = toStatusCode(error, declaredErrorCode);
2611
2730
  const requestId = extractRequestId(rawBody);
2731
+ const observabilityDetails = errorObservabilityDetails(error, declaredErrorCode);
2612
2732
  logProviderError(
2613
2733
  logger,
2614
2734
  provider,
@@ -2620,13 +2740,13 @@ function createServerAppWithCapabilityModules(
2620
2740
  finishRequestCost(requestCost),
2621
2741
  declaredErrorCode,
2622
2742
  proxyTelemetry,
2743
+ observabilityDetails,
2623
2744
  );
2624
2745
  const telemetryHeader = proxyTelemetry.toHeaderValue();
2625
2746
  if (telemetryHeader) c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
2626
2747
  return responseWithErrorObservability(
2627
- c.json(toErrorResponse(error, requestId, declaredErrorCode), status),
2628
- error,
2629
- declaredErrorCode,
2748
+ c.json(toErrorResponse(error, requestId, observabilityDetails), status),
2749
+ observabilityDetails,
2630
2750
  );
2631
2751
  }
2632
2752
  });
@@ -2668,6 +2788,7 @@ function createServerAppWithCapabilityModules(
2668
2788
  } catch (error) {
2669
2789
  const status = toStatusCode(error);
2670
2790
  const requestId = extractRequestId(rawBody);
2791
+ const observabilityDetails = errorObservabilityDetails(error);
2671
2792
  logProviderError(
2672
2793
  logger,
2673
2794
  provider,
@@ -2679,12 +2800,13 @@ function createServerAppWithCapabilityModules(
2679
2800
  finishRequestCost(requestCost),
2680
2801
  undefined,
2681
2802
  proxyTelemetry,
2803
+ observabilityDetails,
2682
2804
  );
2683
2805
  const telemetryHeader = proxyTelemetry.toHeaderValue();
2684
2806
  if (telemetryHeader) c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
2685
2807
  return responseWithErrorObservability(
2686
- c.json(toErrorResponse(error, requestId), status),
2687
- error,
2808
+ c.json(toErrorResponse(error, requestId, observabilityDetails), status),
2809
+ observabilityDetails,
2688
2810
  );
2689
2811
  }
2690
2812
  });
@@ -2726,6 +2848,7 @@ function createServerAppWithCapabilityModules(
2726
2848
  } catch (error) {
2727
2849
  const status = toStatusCode(error);
2728
2850
  const requestId = extractRequestId(rawBody);
2851
+ const observabilityDetails = errorObservabilityDetails(error);
2729
2852
  logProviderError(
2730
2853
  logger,
2731
2854
  provider,
@@ -2737,12 +2860,13 @@ function createServerAppWithCapabilityModules(
2737
2860
  finishRequestCost(requestCost),
2738
2861
  undefined,
2739
2862
  proxyTelemetry,
2863
+ observabilityDetails,
2740
2864
  );
2741
2865
  const telemetryHeader = proxyTelemetry.toHeaderValue();
2742
2866
  if (telemetryHeader) c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
2743
2867
  return responseWithErrorObservability(
2744
- c.json(toErrorResponse(error, requestId), status),
2745
- error,
2868
+ c.json(toErrorResponse(error, requestId, observabilityDetails), status),
2869
+ observabilityDetails,
2746
2870
  );
2747
2871
  }
2748
2872
  });
@@ -2784,6 +2908,7 @@ function createServerAppWithCapabilityModules(
2784
2908
  } catch (error) {
2785
2909
  const status = toStatusCode(error);
2786
2910
  const requestId = extractRequestId(rawBody);
2911
+ const observabilityDetails = errorObservabilityDetails(error);
2787
2912
  logProviderError(
2788
2913
  logger,
2789
2914
  provider,
@@ -2795,12 +2920,13 @@ function createServerAppWithCapabilityModules(
2795
2920
  finishRequestCost(requestCost),
2796
2921
  undefined,
2797
2922
  proxyTelemetry,
2923
+ observabilityDetails,
2798
2924
  );
2799
2925
  const telemetryHeader = proxyTelemetry.toHeaderValue();
2800
2926
  if (telemetryHeader) c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
2801
2927
  return responseWithErrorObservability(
2802
- c.json(toErrorResponse(error, requestId), status),
2803
- error,
2928
+ c.json(toErrorResponse(error, requestId, observabilityDetails), status),
2929
+ observabilityDetails,
2804
2930
  );
2805
2931
  }
2806
2932
  });
@@ -2842,6 +2968,7 @@ function createServerAppWithCapabilityModules(
2842
2968
  } catch (error) {
2843
2969
  const status = toStatusCode(error);
2844
2970
  const requestId = extractRequestId(rawBody);
2971
+ const observabilityDetails = errorObservabilityDetails(error);
2845
2972
  logProviderError(
2846
2973
  logger,
2847
2974
  provider,
@@ -2853,12 +2980,13 @@ function createServerAppWithCapabilityModules(
2853
2980
  finishRequestCost(requestCost),
2854
2981
  undefined,
2855
2982
  proxyTelemetry,
2983
+ observabilityDetails,
2856
2984
  );
2857
2985
  const telemetryHeader = proxyTelemetry.toHeaderValue();
2858
2986
  if (telemetryHeader) c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
2859
2987
  return responseWithErrorObservability(
2860
- c.json(toErrorResponse(error, requestId), status),
2861
- error,
2988
+ c.json(toErrorResponse(error, requestId, observabilityDetails), status),
2989
+ observabilityDetails,
2862
2990
  );
2863
2991
  }
2864
2992
  });
@@ -2900,6 +3028,7 @@ function createServerAppWithCapabilityModules(
2900
3028
  } catch (error) {
2901
3029
  const status = toStatusCode(error);
2902
3030
  const requestId = extractRequestId(rawBody);
3031
+ const observabilityDetails = errorObservabilityDetails(error);
2903
3032
  logProviderError(
2904
3033
  logger,
2905
3034
  provider,
@@ -2911,12 +3040,13 @@ function createServerAppWithCapabilityModules(
2911
3040
  finishRequestCost(requestCost),
2912
3041
  undefined,
2913
3042
  proxyTelemetry,
3043
+ observabilityDetails,
2914
3044
  );
2915
3045
  const telemetryHeader = proxyTelemetry.toHeaderValue();
2916
3046
  if (telemetryHeader) c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
2917
3047
  return responseWithErrorObservability(
2918
- c.json(toErrorResponse(error, requestId), status),
2919
- error,
3048
+ c.json(toErrorResponse(error, requestId, observabilityDetails), status),
3049
+ observabilityDetails,
2920
3050
  );
2921
3051
  }
2922
3052
  });