@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.
- package/CHANGELOG.md +8 -0
- package/bin/apifuse-check.ts +61 -0
- package/bin/apifuse-migrate-shape.ts +202 -0
- package/bin/apifuse-submit-check.ts +1773 -222
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +8 -0
- package/dist/cli/create.js +6 -0
- package/dist/cli/migrate-operation-shape.d.ts +44 -0
- package/dist/cli/migrate-operation-shape.js +113 -0
- package/dist/cli/migrate-provider-shape.d.ts +52 -0
- package/dist/cli/migrate-provider-shape.js +578 -0
- package/dist/cli/templates/provider/provider.json.tpl +6 -0
- package/dist/contract.js +1 -0
- package/dist/define.js +22 -1
- package/dist/error-observability.d.ts +7 -0
- package/dist/error-observability.js +61 -0
- package/dist/errors.d.ts +15 -0
- package/dist/fixture-sanitization.js +13 -3
- package/dist/index.d.ts +1 -1
- package/dist/provider.d.ts +1 -1
- package/dist/runtime/executor.js +11 -1
- package/dist/server/error-observability.d.ts +1 -0
- package/dist/server/error-observability.js +1 -0
- package/dist/server/index.d.ts +2 -1
- package/dist/server/self-test.js +3 -0
- package/dist/server/serve-implementation.d.ts +12 -0
- package/dist/server/serve-implementation.js +174 -66
- package/dist/types.d.ts +18 -10
- package/package.json +1 -1
- package/src/cli/commands.ts +10 -0
- package/src/cli/create.ts +6 -0
- package/src/cli/migrate-operation-shape.ts +184 -0
- package/src/cli/migrate-provider-shape.ts +772 -0
- package/src/cli/templates/provider/provider.json.tpl +6 -0
- package/src/contract.ts +1 -0
- package/src/define.ts +33 -1
- package/src/error-observability.ts +64 -0
- package/src/errors.ts +16 -0
- package/src/fixture-sanitization.ts +19 -3
- package/src/index.ts +1 -0
- package/src/provider.ts +1 -0
- package/src/runtime/executor.ts +13 -1
- package/src/server/error-observability.ts +1 -0
- package/src/server/index.ts +2 -0
- package/src/server/self-test.ts +5 -0
- package/src/server/serve-implementation.ts +214 -84
- 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
|
-
|
|
1139
|
-
if (
|
|
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
|
|
1149
|
-
|
|
1187
|
+
requestId: string | undefined,
|
|
1188
|
+
observabilityDetails: ErrorObservabilityDetails,
|
|
1150
1189
|
): OperationErrorResponse {
|
|
1151
|
-
const observability =
|
|
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
|
|
1205
|
+
const details = providerErrorOption(error, "details");
|
|
1167
1206
|
return {
|
|
1168
1207
|
error: {
|
|
1169
|
-
code: 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
|
|
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
|
|
1273
|
+
category: providerErrorOption(error, "category") ?? "credential_expired",
|
|
1235
1274
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
1236
|
-
retryable: error
|
|
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
|
|
1283
|
+
if (isProviderError(error) && providerErrorCode(error) === MISSING_SECRET_CODE) {
|
|
1245
1284
|
return {
|
|
1246
|
-
category: error
|
|
1285
|
+
category: providerErrorOption(error, "category") ?? "credential_unavailable",
|
|
1247
1286
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
1248
|
-
retryable: error
|
|
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
|
|
1256
|
-
error
|
|
1257
|
-
error
|
|
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
|
|
1298
|
+
providerErrorOption(error, "category") ??
|
|
1260
1299
|
(isProxyPoolCode
|
|
1261
1300
|
? "proxy_pool"
|
|
1262
|
-
: error
|
|
1301
|
+
: providerErrorCode(error) === PROXY_AUTH_IP_DENIED_CODE
|
|
1263
1302
|
? "anti_bot_blocked"
|
|
1264
|
-
: error
|
|
1303
|
+
: providerErrorCode(error) === "transport_timeout"
|
|
1265
1304
|
? "timeout"
|
|
1266
|
-
: 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
|
|
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
|
|
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
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
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
|
|
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
|
|
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
|
|
1370
|
+
category: providerErrorOption(error, "category") ?? rejectionDefault,
|
|
1332
1371
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
1333
|
-
retryable:
|
|
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
|
|
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
|
|
1411
|
+
if (providerErrorCode(error) === PROXY_AUTH_IP_DENIED_CODE) {
|
|
1364
1412
|
return error.message;
|
|
1365
1413
|
}
|
|
1366
|
-
if (error
|
|
1414
|
+
if (providerErrorCode(error) === PROXY_EDGE_AUTH_REJECTED_CODE) {
|
|
1367
1415
|
return error.message;
|
|
1368
1416
|
}
|
|
1369
|
-
if (error
|
|
1417
|
+
if (providerErrorCode(error) === PROXY_POOL_EXHAUSTED_CODE) {
|
|
1370
1418
|
return error.message;
|
|
1371
1419
|
}
|
|
1372
|
-
if (error
|
|
1373
|
-
if (error
|
|
1374
|
-
if (error
|
|
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
|
-
|
|
1405
|
-
|
|
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
|
|
1460
|
+
return providerErrorCode(error) === "transport_timeout" ? 504 : 502;
|
|
1412
1461
|
}
|
|
1413
1462
|
if (isValidationError(error)) {
|
|
1414
|
-
return error
|
|
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
|
-
|
|
1430
|
-
|
|
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
|
-
|
|
1454
|
-
|
|
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
|
-
...(
|
|
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
|
|
1511
|
-
proxyTelemetry
|
|
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
|
-
? (
|
|
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 =
|
|
1626
|
+
const details = observabilityDetails;
|
|
1524
1627
|
const isUnregisteredProviderErrorCode =
|
|
1525
1628
|
status === 500 &&
|
|
1526
1629
|
isProviderError(error) &&
|
|
1527
1630
|
!isValidationError(error) &&
|
|
1528
|
-
typeof
|
|
1529
|
-
!SDK_OWNED_PROVIDER_ERROR_CODES.has(
|
|
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
|
-
|
|
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 (
|
|
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,
|
|
2554
|
-
|
|
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,
|
|
2628
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3048
|
+
c.json(toErrorResponse(error, requestId, observabilityDetails), status),
|
|
3049
|
+
observabilityDetails,
|
|
2920
3050
|
);
|
|
2921
3051
|
}
|
|
2922
3052
|
});
|