@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.
Files changed (65) hide show
  1. package/AUTHORING.md +271 -6
  2. package/CHANGELOG.md +16 -0
  3. package/README.md +26 -2
  4. package/bin/apifuse-pack-types.ts +30 -1
  5. package/bin/apifuse-record.ts +622 -57
  6. package/bin/apifuse-submit-check.ts +43 -10
  7. package/dist/define.d.ts +2 -1
  8. package/dist/define.js +61 -3
  9. package/dist/errors.d.ts +1 -0
  10. package/dist/errors.js +6 -0
  11. package/dist/fixture-sanitization.d.ts +26 -0
  12. package/dist/fixture-sanitization.js +216 -0
  13. package/dist/index.d.ts +3 -2
  14. package/dist/index.js +2 -1
  15. package/dist/provider.d.ts +2 -1
  16. package/dist/provider.js +1 -0
  17. package/dist/runtime/executor.js +17 -2
  18. package/dist/runtime/http.js +86 -32
  19. package/dist/runtime/instrumentation.js +295 -9
  20. package/dist/runtime/native-network.d.ts +53 -0
  21. package/dist/runtime/native-network.js +477 -0
  22. package/dist/runtime/proxy-nodemaven.d.ts +14 -0
  23. package/dist/runtime/proxy-nodemaven.js +20 -2
  24. package/dist/runtime/request-options.d.ts +68 -1
  25. package/dist/runtime/request-options.js +548 -0
  26. package/dist/runtime/stealth.d.ts +3 -1
  27. package/dist/runtime/stealth.js +239 -39
  28. package/dist/server/index.d.ts +2 -2
  29. package/dist/server/index.js +2 -2
  30. package/dist/server/self-test-input-tokens.d.ts +2 -1
  31. package/dist/server/self-test-input-tokens.js +18 -14
  32. package/dist/server/serve.d.ts +9 -0
  33. package/dist/server/serve.js +153 -51
  34. package/dist/server/types.d.ts +3 -0
  35. package/dist/server/types.js +1 -0
  36. package/dist/stateful/stateful-provider-owner-forwarder.js +9 -1
  37. package/dist/stream-evidence.d.ts +74 -0
  38. package/dist/stream-evidence.js +785 -0
  39. package/dist/testing/index.d.ts +1 -1
  40. package/dist/testing/index.js +1 -1
  41. package/dist/testing/run.d.ts +32 -2
  42. package/dist/testing/run.js +451 -19
  43. package/dist/types.d.ts +162 -0
  44. package/package.json +2 -1
  45. package/src/define.ts +81 -3
  46. package/src/errors.ts +9 -0
  47. package/src/fixture-sanitization.ts +247 -0
  48. package/src/index.ts +43 -1
  49. package/src/provider.ts +37 -0
  50. package/src/runtime/executor.ts +22 -2
  51. package/src/runtime/http.ts +144 -38
  52. package/src/runtime/instrumentation.ts +424 -8
  53. package/src/runtime/native-network.ts +600 -0
  54. package/src/runtime/proxy-nodemaven.ts +37 -2
  55. package/src/runtime/request-options.ts +680 -1
  56. package/src/runtime/stealth.ts +293 -40
  57. package/src/server/index.ts +6 -1
  58. package/src/server/self-test-input-tokens.ts +29 -14
  59. package/src/server/serve.ts +190 -68
  60. package/src/server/types.ts +1 -0
  61. package/src/stateful/stateful-provider-owner-forwarder.ts +9 -1
  62. package/src/stream-evidence.ts +988 -0
  63. package/src/testing/index.ts +9 -1
  64. package/src/testing/run.ts +608 -12
  65. package/src/types.ts +194 -0
@@ -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
- details: { retryable: false },
332
+ retryable: observability.retryable,
330
333
  },
331
334
  };
332
335
  }
333
336
  if (isProviderError(error)) {
334
- const details = publicProviderErrorDetails(error);
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
- return 400;
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 = isProviderError(error) ? providerObservabilityDetails(error) : undefined;
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
- ...(isTransportError(error) && error.upstreamStatus
548
- ? { upstreamStatus: error.upstreamStatus }
549
- : {}),
550
- ...(details
551
- ? {
552
- errorCategory: details.category,
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) => c.json({
1154
- error: {
1155
- code: "not_found",
1156
- message: "Not found",
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;
@@ -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>;
@@ -21,6 +21,7 @@ export const ErrorEnvelopeSchema = z.object({
21
21
  code: z.string(),
22
22
  message: z.string(),
23
23
  requestId: z.string().optional(),
24
+ retryable: z.boolean(),
24
25
  fix: z.string().optional(),
25
26
  details: z.unknown().optional(),
26
27
  });
@@ -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 = OperationErrorResponseSchema.safeParse(body);
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 {};