@apifuse/provider-sdk 2.2.0-beta.12 → 2.2.0-beta.14
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/AUTHORING.md +271 -6
- package/CHANGELOG.md +16 -0
- package/README.md +26 -2
- package/bin/apifuse-pack-types.ts +30 -1
- package/bin/apifuse-record.ts +622 -57
- package/bin/apifuse-submit-check.ts +43 -10
- package/dist/define.d.ts +2 -1
- package/dist/define.js +61 -3
- package/dist/errors.d.ts +1 -0
- package/dist/errors.js +6 -0
- package/dist/fixture-sanitization.d.ts +26 -0
- package/dist/fixture-sanitization.js +216 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/provider.d.ts +2 -1
- package/dist/provider.js +1 -0
- package/dist/runtime/executor.js +17 -2
- package/dist/runtime/http.js +86 -32
- package/dist/runtime/instrumentation.js +295 -9
- package/dist/runtime/native-network.d.ts +53 -0
- package/dist/runtime/native-network.js +477 -0
- package/dist/runtime/proxy-nodemaven.d.ts +14 -0
- package/dist/runtime/proxy-nodemaven.js +20 -2
- package/dist/runtime/request-options.d.ts +68 -1
- package/dist/runtime/request-options.js +548 -0
- package/dist/runtime/stealth.d.ts +3 -1
- package/dist/runtime/stealth.js +239 -39
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +2 -2
- package/dist/server/self-test-input-tokens.d.ts +2 -1
- package/dist/server/self-test-input-tokens.js +18 -14
- package/dist/server/serve.d.ts +9 -0
- package/dist/server/serve.js +153 -51
- package/dist/server/types.d.ts +3 -0
- package/dist/server/types.js +1 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +9 -1
- package/dist/stream-evidence.d.ts +74 -0
- package/dist/stream-evidence.js +785 -0
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +1 -1
- package/dist/testing/run.d.ts +32 -2
- package/dist/testing/run.js +451 -19
- package/dist/types.d.ts +162 -0
- package/package.json +2 -1
- package/src/define.ts +81 -3
- package/src/errors.ts +9 -0
- package/src/fixture-sanitization.ts +247 -0
- package/src/index.ts +43 -1
- package/src/provider.ts +37 -0
- package/src/runtime/executor.ts +22 -2
- package/src/runtime/http.ts +144 -38
- package/src/runtime/instrumentation.ts +424 -8
- package/src/runtime/native-network.ts +600 -0
- package/src/runtime/proxy-nodemaven.ts +37 -2
- package/src/runtime/request-options.ts +680 -1
- package/src/runtime/stealth.ts +293 -40
- package/src/server/index.ts +6 -1
- package/src/server/self-test-input-tokens.ts +29 -14
- package/src/server/serve.ts +190 -68
- package/src/server/types.ts +1 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +9 -1
- package/src/stream-evidence.ts +988 -0
- package/src/testing/index.ts +9 -1
- package/src/testing/run.ts +608 -12
- package/src/types.ts +194 -0
package/dist/server/serve.js
CHANGED
|
@@ -3,7 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
import { Hono } from "hono";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { AuthAbortError, createAuthFlowHelpers } from "../auth.js";
|
|
6
|
-
import { AuthError, isProviderError, isSessionExpiredError, isTransportError, ProviderError, } from "../errors.js";
|
|
6
|
+
import { AuthError, isProviderError, isSessionExpiredError, isTransportError, isValidationError, ProviderError, } from "../errors.js";
|
|
7
7
|
import { loadProviderLocaleCatalogs, localizeAuthTurn, } from "../i18n/catalog.js";
|
|
8
8
|
import { categoryForStatus, isRetryableCategory, PROVIDER_OBSERVABILITY_TAXONOMY_VERSION, } from "../observability.js";
|
|
9
9
|
import { createScratchpad } from "../runtime/auth-flow.js";
|
|
@@ -33,6 +33,8 @@ import { resolveSelfTestMasterSecrets } from "./self-test-token.js";
|
|
|
33
33
|
import { AuthFlowRequestSchema, OperationConnectionSchema, OperationRequestSchema, } from "./types.js";
|
|
34
34
|
const DEFAULT_HOST = "0.0.0.0";
|
|
35
35
|
const DEFAULT_PORT = 3000;
|
|
36
|
+
/** Compact SDK-owned error classification emitted separately from the public response body. */
|
|
37
|
+
export const ERROR_OBSERVABILITY_HEADER = "X-ApiFuse-Error-Observability";
|
|
36
38
|
const AUTH_FLOW_LOCALES = ["en", "ko", "ja"];
|
|
37
39
|
const retryResponseMeta = new WeakMap();
|
|
38
40
|
const STATEFUL_INTERNAL_OPERATIONS_ROUTE = "/__apifuse/stateful/operations";
|
|
@@ -320,25 +322,27 @@ function zodDetails(error) {
|
|
|
320
322
|
}));
|
|
321
323
|
}
|
|
322
324
|
function toErrorResponse(error, requestId) {
|
|
325
|
+
const observability = errorObservabilityDetails(error);
|
|
323
326
|
if (error instanceof StatefulRoutingDeadlineError) {
|
|
324
327
|
return {
|
|
325
328
|
error: {
|
|
326
329
|
code: "STATEFUL_FORWARDING_DEADLINE_EXPIRED",
|
|
327
330
|
message: "Stateful forwarding deadline expired.",
|
|
328
331
|
...(requestId ? { requestId } : {}),
|
|
329
|
-
|
|
332
|
+
retryable: observability.retryable,
|
|
330
333
|
},
|
|
331
334
|
};
|
|
332
335
|
}
|
|
333
336
|
if (isProviderError(error)) {
|
|
334
|
-
const details =
|
|
337
|
+
const details = error.details;
|
|
335
338
|
return {
|
|
336
339
|
error: {
|
|
337
340
|
code: error.code ?? "provider_error",
|
|
338
341
|
message: publicProviderErrorMessage(error),
|
|
339
342
|
...(requestId ? { requestId } : {}),
|
|
343
|
+
retryable: observability.retryable,
|
|
340
344
|
...(error.fix ? { fix: error.fix } : {}),
|
|
341
|
-
...(details ? { details } : {}),
|
|
345
|
+
...(details !== undefined ? { details } : {}),
|
|
342
346
|
},
|
|
343
347
|
};
|
|
344
348
|
}
|
|
@@ -348,6 +352,7 @@ function toErrorResponse(error, requestId) {
|
|
|
348
352
|
code: "invalid_request",
|
|
349
353
|
message: "Invalid request body",
|
|
350
354
|
...(requestId ? { requestId } : {}),
|
|
355
|
+
retryable: observability.retryable,
|
|
351
356
|
details: zodDetails(error),
|
|
352
357
|
},
|
|
353
358
|
};
|
|
@@ -363,6 +368,7 @@ function toErrorResponse(error, requestId) {
|
|
|
363
368
|
code: "internal_error",
|
|
364
369
|
message: "Internal error",
|
|
365
370
|
...(requestId ? { requestId } : {}),
|
|
371
|
+
retryable: observability.retryable,
|
|
366
372
|
details: {
|
|
367
373
|
retryable: false,
|
|
368
374
|
category: "internal_error",
|
|
@@ -371,26 +377,6 @@ function toErrorResponse(error, requestId) {
|
|
|
371
377
|
},
|
|
372
378
|
};
|
|
373
379
|
}
|
|
374
|
-
function publicProviderErrorDetails(error) {
|
|
375
|
-
const providerDetails = error.details;
|
|
376
|
-
const observabilityDetails = providerObservabilityDetails(error);
|
|
377
|
-
if (providerDetails === undefined) {
|
|
378
|
-
return observabilityDetails;
|
|
379
|
-
}
|
|
380
|
-
if (observabilityDetails === undefined) {
|
|
381
|
-
return providerDetails;
|
|
382
|
-
}
|
|
383
|
-
if (isPlainRecord(providerDetails) && isPlainRecord(observabilityDetails)) {
|
|
384
|
-
return { ...providerDetails, ...observabilityDetails };
|
|
385
|
-
}
|
|
386
|
-
return {
|
|
387
|
-
provider: providerDetails,
|
|
388
|
-
observability: observabilityDetails,
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
function isPlainRecord(value) {
|
|
392
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
393
|
-
}
|
|
394
380
|
// Accepts `unknown` so the branded guards narrow cleanly from the top: the
|
|
395
381
|
// subtype error classes are structurally compatible with ProviderError, so
|
|
396
382
|
// narrowing from a ProviderError-typed value would collapse the negative branch
|
|
@@ -449,6 +435,48 @@ function providerObservabilityDetails(error) {
|
|
|
449
435
|
...(error.upstreamStatus ? { upstreamStatus: error.upstreamStatus } : {}),
|
|
450
436
|
};
|
|
451
437
|
}
|
|
438
|
+
function errorObservabilityDetails(error) {
|
|
439
|
+
const providerDetails = providerObservabilityDetails(error);
|
|
440
|
+
if (providerDetails)
|
|
441
|
+
return providerDetails;
|
|
442
|
+
if (error instanceof z.ZodError || isValidationError(error)) {
|
|
443
|
+
return {
|
|
444
|
+
category: isProviderError(error) && error.options?.category
|
|
445
|
+
? error.options.category
|
|
446
|
+
: "input_validation",
|
|
447
|
+
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
448
|
+
retryable: isProviderError(error) ? (error.options?.retryable ?? false) : false,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
if (error instanceof StatefulRoutingDeadlineError) {
|
|
452
|
+
return {
|
|
453
|
+
category: "timeout",
|
|
454
|
+
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
455
|
+
retryable: false,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
if (isProviderError(error)) {
|
|
459
|
+
return {
|
|
460
|
+
category: error.options?.category ?? "provider_error",
|
|
461
|
+
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
462
|
+
retryable: error.options?.retryable ?? false,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
category: "internal_error",
|
|
467
|
+
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
468
|
+
retryable: false,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
function responseWithErrorObservability(response, error) {
|
|
472
|
+
const headers = new Headers(response.headers);
|
|
473
|
+
headers.set(ERROR_OBSERVABILITY_HEADER, JSON.stringify(errorObservabilityDetails(error)));
|
|
474
|
+
return new Response(response.body, {
|
|
475
|
+
status: response.status,
|
|
476
|
+
statusText: response.statusText,
|
|
477
|
+
headers,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
452
480
|
function publicProviderErrorMessage(error) {
|
|
453
481
|
if (isTransportError(error)) {
|
|
454
482
|
if (error.code === PROXY_AUTH_IP_DENIED_CODE) {
|
|
@@ -481,9 +509,6 @@ function toStatusCode(error) {
|
|
|
481
509
|
if (error instanceof StatefulRoutingDeadlineError) {
|
|
482
510
|
return 504;
|
|
483
511
|
}
|
|
484
|
-
if (isTransportError(error)) {
|
|
485
|
-
return error.code === "transport_timeout" ? 504 : 502;
|
|
486
|
-
}
|
|
487
512
|
if (isProviderError(error)) {
|
|
488
513
|
switch (error.code) {
|
|
489
514
|
case "AUTH_REQUIRED":
|
|
@@ -509,10 +534,87 @@ function toStatusCode(error) {
|
|
|
509
534
|
case "STATEFUL_FORWARDING_REPLAY_CACHE_FULL":
|
|
510
535
|
return 503;
|
|
511
536
|
}
|
|
512
|
-
|
|
537
|
+
if (isTransportError(error)) {
|
|
538
|
+
return error.code === "transport_timeout" ? 504 : 502;
|
|
539
|
+
}
|
|
540
|
+
if (isValidationError(error)) {
|
|
541
|
+
return error.options?.category === "output_validation" ? 500 : 400;
|
|
542
|
+
}
|
|
543
|
+
return 500;
|
|
513
544
|
}
|
|
514
545
|
return 500;
|
|
515
546
|
}
|
|
547
|
+
// Codes emitted by SDK-owned paths must never be attributed to provider
|
|
548
|
+
// authors by the unregistered-code signal, even when their intentional status
|
|
549
|
+
// is 500. Provider-authored codes not in this registry retain the signal.
|
|
550
|
+
const SDK_OWNED_PROVIDER_ERROR_CODES = new Set([
|
|
551
|
+
"AUTH_PROMPT_UNAVAILABLE",
|
|
552
|
+
"BROWSER_CDP_POOL_REQUIRED",
|
|
553
|
+
"BROWSER_RUNTIME_UNSUPPORTED",
|
|
554
|
+
"STEALTH_RUNTIME_UNSUPPORTED",
|
|
555
|
+
"SSE_EVENT_UNDECLARED",
|
|
556
|
+
"STREAM_EVENT_TOO_LARGE",
|
|
557
|
+
"STREAM_CHUNK_TOO_LARGE",
|
|
558
|
+
"SSE_RESULT_UNSUPPORTED",
|
|
559
|
+
"STREAM_RESULT_UNSUPPORTED",
|
|
560
|
+
"AUTH_FLOW_NOT_CONFIGURED",
|
|
561
|
+
"refresh_not_supported",
|
|
562
|
+
"RUNTIME_UNSUPPORTED",
|
|
563
|
+
"PROVIDER_STATE_UNSUPPORTED",
|
|
564
|
+
"CHOICE_TOKEN_MASTER_SECRET_NOT_CONFIGURED",
|
|
565
|
+
"CHOICE_STATE_PAYLOAD_TOO_LARGE",
|
|
566
|
+
"CHOICE_STATE_UNAVAILABLE",
|
|
567
|
+
"CHOICE_CONTEXT_REQUIRED",
|
|
568
|
+
"unsupported_stealth_cookie_store_version",
|
|
569
|
+
"provider_secret_error",
|
|
570
|
+
"credential_key_error",
|
|
571
|
+
"credential_mode_error",
|
|
572
|
+
"flow_expired",
|
|
573
|
+
"turn_validation_error",
|
|
574
|
+
"context_access_error",
|
|
575
|
+
"UNSUPPORTED_STT_OPTION",
|
|
576
|
+
"INVALID_STT_AUDIO",
|
|
577
|
+
"STT_AUDIO_TOO_LARGE",
|
|
578
|
+
"STT_UPSTREAM_FAILED",
|
|
579
|
+
"INVALID_STT_VERIFICATION_CODE_OPTIONS",
|
|
580
|
+
"NO_CODE_FOUND",
|
|
581
|
+
"AMBIGUOUS_CODE",
|
|
582
|
+
"retry_invalid_policy",
|
|
583
|
+
"retry_unsafe_method",
|
|
584
|
+
"stealth_cookie_store_serialize_failed",
|
|
585
|
+
"response_too_large",
|
|
586
|
+
"transport_stream_unavailable",
|
|
587
|
+
"transport_invalid_method",
|
|
588
|
+
"http_transport_override_unsupported",
|
|
589
|
+
"transport_invalid_url",
|
|
590
|
+
"retry_exhausted",
|
|
591
|
+
"auth_abort_unsafe_data",
|
|
592
|
+
"credentials_auth_missing_credential_keys",
|
|
593
|
+
"credentials_auth_missing_credential",
|
|
594
|
+
"credentials_auth_invalid_login_result",
|
|
595
|
+
"credentials_auth_unknown_challenge",
|
|
596
|
+
"credentials_auth_unknown_pending_challenge",
|
|
597
|
+
"STATEFUL_FORWARDING_NOT_CONFIGURED",
|
|
598
|
+
"STATEFUL_FORWARDING_SIGNATURE_MISSING",
|
|
599
|
+
"STATEFUL_FORWARDING_NONCE_INVALID",
|
|
600
|
+
"STATEFUL_FORWARDING_TIMESTAMP_INVALID",
|
|
601
|
+
"STATEFUL_FORWARDING_SIGNATURE_INVALID",
|
|
602
|
+
"STATEFUL_FORWARDING_REPLAY_DETECTED",
|
|
603
|
+
"STATEFUL_FORWARDING_REPLAY_CACHE_FULL",
|
|
604
|
+
"STATEFUL_FORWARDING_ENVELOPE_INVALID",
|
|
605
|
+
"STATEFUL_FORWARDING_PROVIDER_MISMATCH",
|
|
606
|
+
"STATEFUL_FORWARDING_SOURCE_POD_MISMATCH",
|
|
607
|
+
"STATEFUL_FORWARDING_OWNER_FENCE_INVALID",
|
|
608
|
+
"STATEFUL_FORWARDING_REQUEST_FAILED",
|
|
609
|
+
"STATEFUL_FORWARDING_CONTEXT_MISSING",
|
|
610
|
+
"STATEFUL_FORWARDING_BAD_RESPONSE",
|
|
611
|
+
"STATEFUL_INTERNAL_EXECUTOR_NOT_CONFIGURED",
|
|
612
|
+
"STATEFUL_FILE_FORWARDING_UNSUPPORTED",
|
|
613
|
+
"STATEFUL_CONTROL_PLANE_OPERATION_AMBIGUOUS",
|
|
614
|
+
"STATEFUL_CONTROL_PLANE_REQUEST_FAILED",
|
|
615
|
+
"STATEFUL_CONTROL_PLANE_HTTP_ERROR",
|
|
616
|
+
"STATEFUL_CONTROL_PLANE_INVALID_RESPONSE",
|
|
617
|
+
]);
|
|
516
618
|
function extractRequestId(raw) {
|
|
517
619
|
if (!raw || typeof raw !== "object") {
|
|
518
620
|
return undefined;
|
|
@@ -530,7 +632,12 @@ function logProviderError(logger, provider, kind, route, requestId, error, statu
|
|
|
530
632
|
: "internal_error";
|
|
531
633
|
const errorClass = error instanceof Error ? error.name : typeof error;
|
|
532
634
|
const message = error instanceof Error ? error.message : String(error);
|
|
533
|
-
const details =
|
|
635
|
+
const details = errorObservabilityDetails(error);
|
|
636
|
+
const isUnregisteredProviderErrorCode = status === 500 &&
|
|
637
|
+
isProviderError(error) &&
|
|
638
|
+
!isValidationError(error) &&
|
|
639
|
+
typeof error.code === "string" &&
|
|
640
|
+
!SDK_OWNED_PROVIDER_ERROR_CODES.has(error.code);
|
|
534
641
|
const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
|
|
535
642
|
emit({
|
|
536
643
|
level: status >= 500 ? "error" : "warn",
|
|
@@ -544,15 +651,12 @@ function logProviderError(logger, provider, kind, route, requestId, error, statu
|
|
|
544
651
|
code,
|
|
545
652
|
errorClass,
|
|
546
653
|
message,
|
|
547
|
-
...(
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
taxonomyVersion: details.taxonomyVersion,
|
|
554
|
-
retryable: details.retryable,
|
|
555
|
-
}
|
|
654
|
+
...(details.upstreamStatus ? { upstreamStatus: details.upstreamStatus } : {}),
|
|
655
|
+
errorCategory: details.category,
|
|
656
|
+
taxonomyVersion: details.taxonomyVersion,
|
|
657
|
+
retryable: details.retryable,
|
|
658
|
+
...(isUnregisteredProviderErrorCode
|
|
659
|
+
? { signal: "unregistered_provider_error_code" }
|
|
556
660
|
: {}),
|
|
557
661
|
...(error instanceof z.ZodError ? { issues: zodDetails(error) } : {}),
|
|
558
662
|
});
|
|
@@ -1150,12 +1254,10 @@ export function createServerApp(provider, options = {}) {
|
|
|
1150
1254
|
missingSecrets: missingSecretsAtBoot,
|
|
1151
1255
|
});
|
|
1152
1256
|
}
|
|
1153
|
-
app.notFound((c) =>
|
|
1154
|
-
error:
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
},
|
|
1158
|
-
}, 404));
|
|
1257
|
+
app.notFound((c) => {
|
|
1258
|
+
const error = new ProviderError("Not found", { code: "not_found", retryable: false });
|
|
1259
|
+
return responseWithErrorObservability(c.json(toErrorResponse(error), 404), error);
|
|
1260
|
+
});
|
|
1159
1261
|
app.get("/health", (c) => c.json({
|
|
1160
1262
|
status: "ok",
|
|
1161
1263
|
provider: provider.id,
|
|
@@ -1272,7 +1374,7 @@ export function createServerApp(provider, options = {}) {
|
|
|
1272
1374
|
}
|
|
1273
1375
|
const requestId = extractRequestId(rawBody);
|
|
1274
1376
|
logProviderError(logger, provider, "operation", operation, requestId, error, status, finishRequestCost(requestCost));
|
|
1275
|
-
return c.json(toErrorResponse(error, requestId), status);
|
|
1377
|
+
return responseWithErrorObservability(c.json(toErrorResponse(error, requestId), status), error);
|
|
1276
1378
|
}
|
|
1277
1379
|
});
|
|
1278
1380
|
app.post("/v1/:operation", async (c) => {
|
|
@@ -1306,7 +1408,7 @@ export function createServerApp(provider, options = {}) {
|
|
|
1306
1408
|
const telemetryHeader = proxyTelemetry.toHeaderValue();
|
|
1307
1409
|
if (telemetryHeader)
|
|
1308
1410
|
c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
|
|
1309
|
-
return c.json(toErrorResponse(error, requestId), status);
|
|
1411
|
+
return responseWithErrorObservability(c.json(toErrorResponse(error, requestId), status), error);
|
|
1310
1412
|
}
|
|
1311
1413
|
});
|
|
1312
1414
|
app.post("/auth/start", async (c) => {
|
|
@@ -1326,7 +1428,7 @@ export function createServerApp(provider, options = {}) {
|
|
|
1326
1428
|
const status = toStatusCode(error);
|
|
1327
1429
|
const requestId = extractRequestId(rawBody);
|
|
1328
1430
|
logProviderError(logger, provider, "auth", "start", requestId, error, status, finishRequestCost(requestCost));
|
|
1329
|
-
return c.json(toErrorResponse(error, requestId), status);
|
|
1431
|
+
return responseWithErrorObservability(c.json(toErrorResponse(error, requestId), status), error);
|
|
1330
1432
|
}
|
|
1331
1433
|
});
|
|
1332
1434
|
app.post("/auth/continue", async (c) => {
|
|
@@ -1346,7 +1448,7 @@ export function createServerApp(provider, options = {}) {
|
|
|
1346
1448
|
const status = toStatusCode(error);
|
|
1347
1449
|
const requestId = extractRequestId(rawBody);
|
|
1348
1450
|
logProviderError(logger, provider, "auth", "continue", requestId, error, status, finishRequestCost(requestCost));
|
|
1349
|
-
return c.json(toErrorResponse(error, requestId), status);
|
|
1451
|
+
return responseWithErrorObservability(c.json(toErrorResponse(error, requestId), status), error);
|
|
1350
1452
|
}
|
|
1351
1453
|
});
|
|
1352
1454
|
app.post("/auth/poll", async (c) => {
|
|
@@ -1366,7 +1468,7 @@ export function createServerApp(provider, options = {}) {
|
|
|
1366
1468
|
const status = toStatusCode(error);
|
|
1367
1469
|
const requestId = extractRequestId(rawBody);
|
|
1368
1470
|
logProviderError(logger, provider, "auth", "poll", requestId, error, status, finishRequestCost(requestCost));
|
|
1369
|
-
return c.json(toErrorResponse(error, requestId), status);
|
|
1471
|
+
return responseWithErrorObservability(c.json(toErrorResponse(error, requestId), status), error);
|
|
1370
1472
|
}
|
|
1371
1473
|
});
|
|
1372
1474
|
app.post("/auth/refresh", async (c) => {
|
|
@@ -1386,7 +1488,7 @@ export function createServerApp(provider, options = {}) {
|
|
|
1386
1488
|
const status = toStatusCode(error);
|
|
1387
1489
|
const requestId = extractRequestId(rawBody);
|
|
1388
1490
|
logProviderError(logger, provider, "auth", "refresh", requestId, error, status, finishRequestCost(requestCost));
|
|
1389
|
-
return c.json(toErrorResponse(error, requestId), status);
|
|
1491
|
+
return responseWithErrorObservability(c.json(toErrorResponse(error, requestId), status), error);
|
|
1390
1492
|
}
|
|
1391
1493
|
});
|
|
1392
1494
|
app.post("/auth/disconnect", async (c) => {
|
|
@@ -1406,7 +1508,7 @@ export function createServerApp(provider, options = {}) {
|
|
|
1406
1508
|
const status = toStatusCode(error);
|
|
1407
1509
|
const requestId = extractRequestId(rawBody);
|
|
1408
1510
|
logProviderError(logger, provider, "auth", "disconnect", requestId, error, status, finishRequestCost(requestCost));
|
|
1409
|
-
return c.json(toErrorResponse(error, requestId), status);
|
|
1511
|
+
return responseWithErrorObservability(c.json(toErrorResponse(error, requestId), status), error);
|
|
1410
1512
|
}
|
|
1411
1513
|
});
|
|
1412
1514
|
return app;
|
package/dist/server/types.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export declare const ErrorEnvelopeSchema: z.ZodObject<{
|
|
|
42
42
|
code: z.ZodString;
|
|
43
43
|
message: z.ZodString;
|
|
44
44
|
requestId: z.ZodOptional<z.ZodString>;
|
|
45
|
+
retryable: z.ZodBoolean;
|
|
45
46
|
fix: z.ZodOptional<z.ZodString>;
|
|
46
47
|
details: z.ZodOptional<z.ZodUnknown>;
|
|
47
48
|
}, z.core.$strip>;
|
|
@@ -84,6 +85,7 @@ export declare const OperationErrorResponseSchema: z.ZodObject<{
|
|
|
84
85
|
code: z.ZodString;
|
|
85
86
|
message: z.ZodString;
|
|
86
87
|
requestId: z.ZodOptional<z.ZodString>;
|
|
88
|
+
retryable: z.ZodBoolean;
|
|
87
89
|
fix: z.ZodOptional<z.ZodString>;
|
|
88
90
|
details: z.ZodOptional<z.ZodUnknown>;
|
|
89
91
|
}, z.core.$strip>;
|
|
@@ -121,6 +123,7 @@ export declare const AuthFlowErrorResponseSchema: z.ZodObject<{
|
|
|
121
123
|
code: z.ZodString;
|
|
122
124
|
message: z.ZodString;
|
|
123
125
|
requestId: z.ZodOptional<z.ZodString>;
|
|
126
|
+
retryable: z.ZodBoolean;
|
|
124
127
|
fix: z.ZodOptional<z.ZodString>;
|
|
125
128
|
details: z.ZodOptional<z.ZodUnknown>;
|
|
126
129
|
}, z.core.$strip>;
|
package/dist/server/types.js
CHANGED
|
@@ -9,6 +9,14 @@ export const STATEFUL_FORWARDING_NONCE_HEADER = "x-apifuse-stateful-nonce";
|
|
|
9
9
|
export const STATEFUL_FORWARDING_SOURCE_POD_HEADER = "x-apifuse-stateful-source-pod";
|
|
10
10
|
const MAX_FORWARDED_HEADERS = 32;
|
|
11
11
|
const MAX_FORWARDED_HEADER_BYTES = 8 * 1024;
|
|
12
|
+
// Inbound compatibility boundary for rolling deploys: older owner pods omit
|
|
13
|
+
// top-level retryable. Emitted responses remain strict via
|
|
14
|
+
// OperationErrorResponseSchema.
|
|
15
|
+
const ForwardedOperationErrorResponseSchema = OperationErrorResponseSchema.extend({
|
|
16
|
+
error: OperationErrorResponseSchema.shape.error.extend({
|
|
17
|
+
retryable: z.boolean().optional().default(false),
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
12
20
|
const SENSITIVE_HEADER_NAMES = new Set([
|
|
13
21
|
"authorization",
|
|
14
22
|
"cookie",
|
|
@@ -183,7 +191,7 @@ async function parseForwardedResponse(response) {
|
|
|
183
191
|
if (response.ok && success.success) {
|
|
184
192
|
return { output: success.data.data };
|
|
185
193
|
}
|
|
186
|
-
const error =
|
|
194
|
+
const error = ForwardedOperationErrorResponseSchema.safeParse(body);
|
|
187
195
|
if (error.success) {
|
|
188
196
|
throw new StatefulOwnerForwardingError({
|
|
189
197
|
code: error.data.error.code,
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type JsonValue } from "./contract-json.js";
|
|
2
|
+
import type { HttpStreamResponse } from "./types.js";
|
|
3
|
+
export declare const STREAM_PREVIEW_BYTES = 4096;
|
|
4
|
+
export declare const STREAM_FINALIZE_TIMEOUT_MS = 30000;
|
|
5
|
+
declare const STREAM_EVIDENCE_HEADER_NAMES: readonly ["content-disposition", "content-length", "content-type"];
|
|
6
|
+
type StreamEvidenceHeaderName = (typeof STREAM_EVIDENCE_HEADER_NAMES)[number];
|
|
7
|
+
export type StreamEvidenceHeaders = Partial<Record<StreamEvidenceHeaderName, string>>;
|
|
8
|
+
export interface StreamEvidenceRequest {
|
|
9
|
+
ordinal: number;
|
|
10
|
+
method: string;
|
|
11
|
+
path: string;
|
|
12
|
+
}
|
|
13
|
+
declare const STREAM_EVIDENCE_REDACTION_REASONS: readonly ["high-entropy-token", "malformed-json", "pem-private-key", "sanitized-preview-too-large", "sanitizer-output-invalid", "sensitive-delimited-column", "textual-xml", "truncated-form", "truncated-json", "undecodable-text"];
|
|
14
|
+
export type StreamEvidenceRedactionReason = (typeof STREAM_EVIDENCE_REDACTION_REASONS)[number];
|
|
15
|
+
export interface StreamEvidenceRecord {
|
|
16
|
+
__apifuse_stream__: true;
|
|
17
|
+
status: number;
|
|
18
|
+
ok: boolean;
|
|
19
|
+
headers: StreamEvidenceHeaders;
|
|
20
|
+
body_sha256: string;
|
|
21
|
+
body_bytes: number;
|
|
22
|
+
body_preview_base64: string;
|
|
23
|
+
request?: StreamEvidenceRequest;
|
|
24
|
+
preview_sanitized?: true;
|
|
25
|
+
preview_redaction_reason?: StreamEvidenceRedactionReason;
|
|
26
|
+
}
|
|
27
|
+
export type StreamEvidenceReplayResponse = HttpStreamResponse & {
|
|
28
|
+
evidence_only: true;
|
|
29
|
+
body_sha256: string;
|
|
30
|
+
body_bytes: number;
|
|
31
|
+
preview_sanitized?: true;
|
|
32
|
+
preview_redaction_reason?: StreamEvidenceRedactionReason;
|
|
33
|
+
};
|
|
34
|
+
export interface StreamEvidenceCapture {
|
|
35
|
+
response: HttpStreamResponse;
|
|
36
|
+
getEvidence(): Promise<StreamEvidenceRecord>;
|
|
37
|
+
}
|
|
38
|
+
export type StreamCaptureGroupItem = {
|
|
39
|
+
kind: "stream";
|
|
40
|
+
evidence: StreamEvidenceRecord;
|
|
41
|
+
} | {
|
|
42
|
+
kind: "response";
|
|
43
|
+
value: JsonValue;
|
|
44
|
+
};
|
|
45
|
+
export interface StreamCaptureGroup {
|
|
46
|
+
items: StreamCaptureGroupItem[];
|
|
47
|
+
}
|
|
48
|
+
export interface StreamCaptureEnvelope {
|
|
49
|
+
__apifuse_capture__: true;
|
|
50
|
+
items: StreamCaptureGroupItem[];
|
|
51
|
+
}
|
|
52
|
+
export type StreamEvidenceCaptureOptions = {
|
|
53
|
+
requestUrl: string;
|
|
54
|
+
sanitizeFixture?: (value: JsonValue) => JsonValue;
|
|
55
|
+
request?: StreamEvidenceRequest;
|
|
56
|
+
finalizeTimeoutMs?: number;
|
|
57
|
+
};
|
|
58
|
+
export declare function parseStreamEvidenceRecord(value: unknown): StreamEvidenceRecord;
|
|
59
|
+
export declare function isStreamEvidenceRecord(value: unknown): value is StreamEvidenceRecord;
|
|
60
|
+
export declare function findStreamEvidenceRecord(value: unknown): StreamEvidenceRecord | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Selects every stream captured by the latest recording invocation, in stream call order.
|
|
63
|
+
* Evidence without request ordinals uses the legacy latest-record-only behavior.
|
|
64
|
+
*/
|
|
65
|
+
export declare function findStreamEvidenceRecords(value: unknown): StreamEvidenceRecord[];
|
|
66
|
+
/** Returns a tagged latest mixed response group used by evidence-only snapshot replay. */
|
|
67
|
+
export declare function findStreamCaptureGroup(value: unknown): StreamCaptureGroup | undefined;
|
|
68
|
+
/** Creates a discriminated invocation envelope so ordinary arrays cannot mimic capture timelines. */
|
|
69
|
+
export declare function createStreamCaptureEnvelope(items: StreamCaptureGroupItem[]): StreamCaptureEnvelope;
|
|
70
|
+
export declare function isStreamEvidenceReplayResponse(response: HttpStreamResponse): response is StreamEvidenceReplayResponse;
|
|
71
|
+
export declare function captureStreamEvidence(response: HttpStreamResponse, options: StreamEvidenceCaptureOptions): StreamEvidenceCapture;
|
|
72
|
+
export declare function replayStreamEvidence(evidence: StreamEvidenceRecord): StreamEvidenceReplayResponse;
|
|
73
|
+
export declare function hasStreamEvidenceMarker(value: unknown): boolean;
|
|
74
|
+
export {};
|