@apifuse/provider-sdk 2.2.0-beta.3 → 2.2.0-beta.31
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 +493 -5
- package/CHANGELOG.md +131 -1
- package/README.md +52 -6
- package/SUBMISSION.md +1 -1
- package/bin/apifuse-check.ts +106 -62
- package/bin/apifuse-create.ts +1 -1
- package/bin/apifuse-dev.ts +63 -55
- package/bin/apifuse-pack-check.ts +22 -2
- package/bin/apifuse-pack-smoke.ts +78 -82
- package/bin/apifuse-pack-types.ts +583 -0
- package/bin/apifuse-perf.ts +59 -140
- package/bin/apifuse-record.ts +698 -113
- package/bin/apifuse-submit-check.ts +517 -44
- package/bin/apifuse-sync-assets.ts +117 -0
- package/bin/apifuse.ts +1 -1
- package/bin/submit-check-delimited-text.ts +50 -0
- package/bin/submit-check-xml.ts +1 -1
- package/dist/auth-turn/index.d.ts +4 -4
- package/dist/auth-turn/index.js +1 -1
- package/dist/auth.d.ts +16 -2
- package/dist/auth.js +76 -18
- package/dist/ceremonies/index.d.ts +9 -1
- package/dist/ceremonies/index.js +117 -29
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +8 -0
- package/dist/cli/create.d.ts +3 -0
- package/dist/cli/create.js +34 -35
- package/dist/cli/prompt-assets.d.ts +80 -0
- package/dist/cli/prompt-assets.js +743 -0
- package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/dist/cli/templates/provider/README.md.tpl +4 -4
- package/dist/config/loader.d.ts +176 -17
- package/dist/config/loader.js +434 -161
- package/dist/contract-serialization.d.ts +2 -2
- package/dist/contract-serialization.js +7 -14
- package/dist/contract-types.d.ts +3 -2
- package/dist/contract.d.ts +3 -3
- package/dist/contract.js +6 -6
- package/dist/declaration-validation.d.ts +23 -0
- package/dist/declaration-validation.js +159 -0
- package/dist/define.d.ts +13 -1
- package/dist/define.js +391 -122
- package/dist/dev.d.ts +1 -1
- package/dist/dev.js +1 -1
- package/dist/error-resolution.d.ts +4 -0
- package/dist/error-resolution.js +123 -0
- package/dist/errors.d.ts +19 -1
- package/dist/errors.js +41 -3
- package/dist/fixture-sanitization.d.ts +26 -0
- package/dist/fixture-sanitization.js +216 -0
- package/dist/i18n/catalog.d.ts +2 -2
- package/dist/i18n/catalog.js +4 -10
- package/dist/i18n/index.d.ts +2 -2
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/keys.d.ts +2 -2
- package/dist/index.d.ts +50 -42
- package/dist/index.js +41 -37
- package/dist/lint.d.ts +6 -1
- package/dist/lint.js +370 -18
- package/dist/native-address.d.ts +43 -0
- package/dist/native-address.js +281 -0
- package/dist/native-egress-policy.d.ts +31 -0
- package/dist/native-egress-policy.js +288 -0
- package/dist/observability.d.ts +5 -2
- package/dist/observability.js +48 -1
- package/dist/provider.d.ts +13 -11
- package/dist/provider.js +10 -9
- package/dist/public-schema-field-lint.d.ts +1 -1
- package/dist/recipes/gov-api.js +1 -1
- package/dist/runtime/auth-flow.d.ts +3 -1
- package/dist/runtime/auth-flow.js +8 -3
- package/dist/runtime/browser.d.ts +1 -1
- package/dist/runtime/browser.js +138 -40
- package/dist/runtime/cache.d.ts +2 -1
- package/dist/runtime/cache.js +173 -23
- package/dist/runtime/choice-wordlist.d.ts +9 -0
- package/dist/runtime/choice-wordlist.js +138 -0
- package/dist/runtime/choice.d.ts +14 -1
- package/dist/runtime/choice.js +566 -101
- package/dist/runtime/credential.d.ts +1 -1
- package/dist/runtime/credential.js +1 -1
- package/dist/runtime/env.d.ts +1 -1
- package/dist/runtime/executor.d.ts +1 -1
- package/dist/runtime/executor.js +25 -3
- package/dist/runtime/http.d.ts +3 -2
- package/dist/runtime/http.js +517 -55
- package/dist/runtime/insights.d.ts +1 -1
- package/dist/runtime/insights.js +6 -13
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +371 -23
- package/dist/runtime/keyring.js +1 -1
- package/dist/runtime/namespace.js +1 -1
- package/dist/runtime/native-network-errors.d.ts +33 -0
- package/dist/runtime/native-network-errors.js +69 -0
- package/dist/runtime/native-network.d.ts +96 -0
- package/dist/runtime/native-network.js +1232 -0
- package/dist/runtime/ocr.d.ts +29 -0
- package/dist/runtime/ocr.js +440 -0
- package/dist/runtime/otlp.d.ts +1 -1
- package/dist/runtime/perf.d.ts +1 -1
- package/dist/runtime/provider.d.ts +1 -1
- package/dist/runtime/provider.js +1 -2
- package/dist/runtime/proxy-errors.d.ts +1 -1
- package/dist/runtime/proxy-errors.js +9 -7
- package/dist/runtime/proxy-nodemaven.d.ts +56 -0
- package/dist/runtime/proxy-nodemaven.js +146 -0
- package/dist/runtime/proxy-retry-policy.d.ts +2 -2
- package/dist/runtime/proxy-retry-policy.js +2 -2
- package/dist/runtime/proxy-telemetry.d.ts +2 -1
- package/dist/runtime/proxy-telemetry.js +58 -52
- package/dist/runtime/redirects.d.ts +29 -0
- package/dist/runtime/redirects.js +36 -0
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +5 -5
- package/dist/runtime/request-options.d.ts +68 -1
- package/dist/runtime/request-options.js +548 -0
- package/dist/runtime/resolver-config.d.ts +6 -0
- package/dist/runtime/resolver-config.js +6 -0
- package/dist/runtime/resolver-public.d.ts +1 -0
- package/dist/runtime/resolver-public.js +1 -0
- package/dist/runtime/resolver-shared.d.ts +3 -0
- package/dist/runtime/resolver-shared.js +12 -0
- package/dist/runtime/resolver-vendors/bindings.d.ts +48 -0
- package/dist/runtime/resolver-vendors/bindings.js +40 -0
- package/dist/runtime/resolver-vendors/browser.d.ts +20 -0
- package/dist/runtime/resolver-vendors/browser.js +282 -0
- package/dist/runtime/resolver-vendors/hosts.d.ts +2 -0
- package/dist/runtime/resolver-vendors/hosts.js +33 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
- package/dist/runtime/resolver-vendors/twocaptcha.js +368 -0
- package/dist/runtime/resolver-vendors/types.d.ts +83 -0
- package/dist/runtime/resolver-vendors/types.js +69 -0
- package/dist/runtime/resolver.d.ts +59 -0
- package/dist/runtime/resolver.js +705 -0
- package/dist/runtime/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/runtime/state.d.ts +5 -2
- package/dist/runtime/state.js +280 -74
- package/dist/runtime/stealth-cookies.d.ts +20 -0
- package/dist/runtime/stealth-cookies.js +111 -0
- package/dist/runtime/stealth.d.ts +30 -5
- package/dist/runtime/stealth.js +523 -259
- package/dist/runtime/stt.d.ts +1 -1
- package/dist/runtime/stt.js +12 -27
- package/dist/runtime/timeout.d.ts +5 -0
- package/dist/runtime/timeout.js +12 -0
- package/dist/runtime/trace.d.ts +2 -2
- package/dist/runtime/trace.js +2 -4
- package/dist/runtime/waterfall.d.ts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +7 -15
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/index.d.ts +7 -7
- package/dist/server/index.js +6 -6
- package/dist/server/self-test-input-tokens.d.ts +2 -1
- package/dist/server/self-test-input-tokens.js +18 -14
- package/dist/server/self-test-redaction.d.ts +1 -1
- package/dist/server/self-test-redaction.js +1 -1
- package/dist/server/self-test.d.ts +117 -3
- package/dist/server/self-test.js +787 -151
- package/dist/server/serve-implementation.d.ts +210 -0
- package/dist/server/serve-implementation.js +2078 -0
- package/dist/server/serve.d.ts +1 -70
- package/dist/server/serve.js +1 -1143
- package/dist/server/types.d.ts +34 -9
- package/dist/server/types.js +8 -1
- package/dist/stateful/errors.d.ts +19 -0
- package/dist/stateful/errors.js +24 -0
- package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
- package/dist/stateful/http-provider-event-emitter.js +237 -0
- package/dist/stateful/http-session-owner-registry.d.ts +44 -0
- package/dist/stateful/http-session-owner-registry.js +210 -0
- package/dist/stateful/index.d.ts +18 -0
- package/dist/stateful/index.js +18 -0
- package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
- package/dist/stateful/provider-event-delivery-failures.js +43 -0
- package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
- package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
- package/dist/stateful/provider-event-pipeline.d.ts +50 -0
- package/dist/stateful/provider-event-pipeline.js +1 -0
- package/dist/stateful/provider-events.d.ts +101 -0
- package/dist/stateful/provider-events.js +289 -0
- package/dist/stateful/session-key.d.ts +15 -0
- package/dist/stateful/session-key.js +86 -0
- package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
- package/dist/stateful/stateful-provider-adapter-context.js +42 -0
- package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
- package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
- package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
- package/dist/stateful/stateful-provider-adapter.js +287 -0
- package/dist/stateful/stateful-provider-observability.d.ts +62 -0
- package/dist/stateful/stateful-provider-observability.js +161 -0
- package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +215 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
- package/dist/stateful/stateful-provider-runtime-context.js +60 -0
- package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
- package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
- package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
- package/dist/stateful/stateful-provider-session-routing.js +345 -0
- package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
- package/dist/stateful/stateful-provider-session-runtime.js +245 -0
- package/dist/stateful-signing.d.ts +18 -0
- package/dist/stateful-signing.js +27 -0
- package/dist/stealth/profiles.d.ts +1 -1
- package/dist/stealth/profiles.js +21 -21
- package/dist/stream-evidence.d.ts +74 -0
- package/dist/stream-evidence.js +785 -0
- package/dist/stream.d.ts +1 -1
- package/dist/stream.js +7 -1
- package/dist/testing/index.d.ts +3 -2
- package/dist/testing/index.js +3 -2
- package/dist/testing/run.d.ts +32 -2
- package/dist/testing/run.js +488 -28
- package/dist/types.d.ts +566 -19
- package/dist/types.js +1 -0
- package/dist/user-input.d.ts +30 -0
- package/dist/user-input.js +66 -0
- package/package.json +42 -7
- package/src/auth-turn/index.ts +2 -2
- package/src/auth.ts +146 -86
- package/src/ceremonies/index.ts +167 -92
- package/src/cli/commands.ts +10 -0
- package/src/cli/create.ts +42 -35
- package/src/cli/prompt-assets.ts +865 -0
- package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/src/cli/templates/provider/README.md.tpl +4 -4
- package/src/config/loader.ts +667 -289
- package/src/contract-serialization.ts +10 -18
- package/src/contract-types.ts +3 -2
- package/src/contract.ts +14 -28
- package/src/declaration-validation.ts +202 -0
- package/src/define.ts +631 -495
- package/src/dev.ts +4 -9
- package/src/error-resolution.ts +128 -0
- package/src/errors.ts +56 -11
- package/src/fixture-sanitization.ts +247 -0
- package/src/i18n/catalog.ts +10 -32
- package/src/i18n/index.ts +2 -2
- package/src/i18n/keys.ts +5 -11
- package/src/index.ts +158 -44
- package/src/lint.ts +488 -154
- package/src/native-address.ts +340 -0
- package/src/native-egress-policy.ts +358 -0
- package/src/observability.ts +51 -1
- package/src/provider.ts +66 -11
- package/src/public-schema-field-lint.ts +7 -33
- package/src/recipes/gov-api.ts +2 -5
- package/src/runtime/auth-flow.ts +13 -7
- package/src/runtime/browser.ts +252 -207
- package/src/runtime/cache.ts +209 -81
- package/src/runtime/choice-wordlist.ts +145 -0
- package/src/runtime/choice.ts +758 -197
- package/src/runtime/credential.ts +2 -2
- package/src/runtime/env.ts +1 -1
- package/src/runtime/executor.ts +37 -19
- package/src/runtime/http.ts +645 -65
- package/src/runtime/insights.ts +15 -53
- package/src/runtime/instrumentation.ts +530 -67
- package/src/runtime/keyring.ts +7 -19
- package/src/runtime/namespace.ts +2 -7
- package/src/runtime/native-network-errors.ts +99 -0
- package/src/runtime/native-network.ts +1605 -0
- package/src/runtime/ocr.ts +523 -0
- package/src/runtime/otlp.ts +12 -23
- package/src/runtime/perf.ts +1 -1
- package/src/runtime/provider.ts +4 -9
- package/src/runtime/proxy-errors.ts +29 -42
- package/src/runtime/proxy-nodemaven.ts +221 -0
- package/src/runtime/proxy-retry-policy.ts +3 -3
- package/src/runtime/proxy-telemetry.ts +84 -77
- package/src/runtime/redirects.ts +66 -0
- package/src/runtime/redis.ts +10 -13
- package/src/runtime/request-options.ts +679 -9
- package/src/runtime/resolver-config.ts +6 -0
- package/src/runtime/resolver-public.ts +18 -0
- package/src/runtime/resolver-shared.ts +17 -0
- package/src/runtime/resolver-vendors/bindings.ts +56 -0
- package/src/runtime/resolver-vendors/browser.ts +408 -0
- package/src/runtime/resolver-vendors/hosts.ts +38 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +500 -0
- package/src/runtime/resolver-vendors/types.ts +173 -0
- package/src/runtime/resolver.ts +1060 -0
- package/src/runtime/secrets.ts +64 -0
- package/src/runtime/state.ts +399 -161
- package/src/runtime/stealth-cookies.ts +132 -0
- package/src/runtime/stealth.ts +681 -295
- package/src/runtime/stt.ts +39 -113
- package/src/runtime/timeout.ts +18 -0
- package/src/runtime/trace.ts +14 -44
- package/src/runtime/waterfall.ts +5 -18
- package/src/schema.ts +23 -84
- package/src/serve.ts +6 -1
- package/src/server/index.ts +30 -7
- package/src/server/self-test-input-tokens.ts +29 -14
- package/src/server/self-test-redaction.ts +2 -2
- package/src/server/self-test.ts +1030 -180
- package/src/server/serve-implementation.ts +3062 -0
- package/src/server/serve.ts +1 -1781
- package/src/server/types.ts +12 -13
- package/src/stateful/README.md +146 -0
- package/src/stateful/errors.ts +35 -0
- package/src/stateful/http-provider-event-emitter.ts +314 -0
- package/src/stateful/http-session-owner-registry.ts +306 -0
- package/src/stateful/index.ts +18 -0
- package/src/stateful/provider-event-delivery-failures.ts +80 -0
- package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
- package/src/stateful/provider-event-pipeline.ts +61 -0
- package/src/stateful/provider-events.ts +462 -0
- package/src/stateful/session-key.ts +111 -0
- package/src/stateful/stateful-provider-adapter-context.ts +59 -0
- package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
- package/src/stateful/stateful-provider-adapter.ts +562 -0
- package/src/stateful/stateful-provider-observability.ts +261 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
- package/src/stateful/stateful-provider-runtime-context.ts +92 -0
- package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
- package/src/stateful/stateful-provider-session-routing.ts +546 -0
- package/src/stateful/stateful-provider-session-runtime.ts +403 -0
- package/src/stateful-signing.ts +46 -0
- package/src/stealth/profiles.ts +27 -33
- package/src/stream-evidence.ts +988 -0
- package/src/stream.ts +16 -20
- package/src/testing/index.ts +11 -2
- package/src/testing/run.ts +668 -74
- package/src/types.ts +665 -35
- package/src/user-input.ts +118 -0
- package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
- package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
- /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
package/AUTHORING.md
CHANGED
|
@@ -18,6 +18,59 @@
|
|
|
18
18
|
|
|
19
19
|
Provider code is the declaration input to the internal platform registry. The public SDK owns provider authoring/runtime ergonomics; internal docs, deploy, and discovery projections are built downstream from those declarations. `bun run lint:providers` enforces provider authoring standards.
|
|
20
20
|
|
|
21
|
+
### User-input round-trips: never dead-end (`needs_input`)
|
|
22
|
+
|
|
23
|
+
A mutation MUST NOT fail for a problem the end user can resolve by choosing among live options (a required menu/course selection, a form question, a stale-but-recoverable state token). Throwing an error there strands the consuming agent: consumer error-shaping layers routinely strip error metadata, and a model that only sees "error" narrates failure to the user instead of relaying the choice.
|
|
24
|
+
|
|
25
|
+
Return the official success-shaped contract from `src/user-input.ts` instead (`ProviderNeedsInputPayload`, guard `isProviderNeedsInputPayload`):
|
|
26
|
+
|
|
27
|
+
- `status: "needs_input"` plus `required_selections` — only the still-pending questions, with human-readable `label`s and `valid_options` the agent relays verbatim. The agent never chooses for the user; anything with no real choice (agreement checkboxes, single-option required groups) is the provider's job to auto-answer.
|
|
28
|
+
- `selected_options` — selections already settled, echoed so the retry keeps them (copy them back and add the user's new answers).
|
|
29
|
+
- a freshly minted provider state token in the same response, so the retry never races an expired token.
|
|
30
|
+
|
|
31
|
+
No retry templates, next-action routing, or other agent choreography: provider payloads carry upstream-backed data only, and the consumer owns how the ask is phrased and how the retry call is shaped.
|
|
32
|
+
|
|
33
|
+
Declare the union in the operation `output` schema (`z.union([CreatedSchema, NeedsInputSchema])`). Reserve hard errors for genuinely unrecoverable flows (payment-gated, unsupported input kinds) and state the concrete reason in the error `message` itself, not only in `details`. Reference implementation: `providers/catchtable` `reserve` in the platform monorepo.
|
|
34
|
+
|
|
35
|
+
### Attempt tokens: the server carries the decisions
|
|
36
|
+
|
|
37
|
+
When a mutation needs more than one user decision (or one decision plus a
|
|
38
|
+
final go/no-go), do not make the agent re-send accumulated state across
|
|
39
|
+
rounds — weak models drop or corrupt it. Split the operation into a
|
|
40
|
+
**prepare/confirm pair** driven by a server-held attempt record:
|
|
41
|
+
|
|
42
|
+
- The prepare operation is non-destructive. A start call takes only the
|
|
43
|
+
scalar intent fields; every response returns a fresh `attempt_token`
|
|
44
|
+
referencing a server-side record (`ctx.choice.issue` with
|
|
45
|
+
`storage.mode: "server"`) that stores every settled decision. Continue
|
|
46
|
+
calls take `attempt_token` plus only the NEW answers.
|
|
47
|
+
- `needs_input` rounds list only the still-pending selections; settled
|
|
48
|
+
decisions may ride along in a display-only field but are never re-sent.
|
|
49
|
+
- When nothing is pending, the prepare operation returns `status: "ready"`
|
|
50
|
+
with a human-readable summary — the consumer's user-facing confirmation.
|
|
51
|
+
- The confirm operation is the only mutation and takes exactly
|
|
52
|
+
`{attempt_token}`. It re-validates everything live before executing and
|
|
53
|
+
returns `needs_input` (fresh token) instead of proceeding when upstream
|
|
54
|
+
drift invalidates a stored decision — never substitute a different option
|
|
55
|
+
for what the user picked.
|
|
56
|
+
- Expired or foreign tokens fail factually (nothing happened; start a new
|
|
57
|
+
attempt with the scalar fields) — no answer salvage from a dead token.
|
|
58
|
+
- **The provider must enforce consumption itself.** `ctx.choice` server
|
|
59
|
+
storage keeps tokens parseable until TTL — `parse` does not invalidate
|
|
60
|
+
them, so a confirm handler that only parses can be replayed into a second
|
|
61
|
+
booking or payment. After a successful execution, record the result under
|
|
62
|
+
the token's digest in `ctx.state` and make replays idempotent: a repeated
|
|
63
|
+
confirm returns the original created payload without touching upstream,
|
|
64
|
+
and later prepare rounds on the consumed token fail factually with the
|
|
65
|
+
existing reference. Record the result only after upstream success, so an
|
|
66
|
+
interrupted confirm stays retryable.
|
|
67
|
+
|
|
68
|
+
The invariant behind all of it: complex flow state is the system's job, not
|
|
69
|
+
the model's. The model carries exactly one opaque key between calls.
|
|
70
|
+
Reference implementations: `providers/catchtable` `reserve`/`reserve-confirm`
|
|
71
|
+
(including the consume-on-success guard) and `providers/modu-parking`
|
|
72
|
+
payment state tokens in the platform monorepo.
|
|
73
|
+
|
|
21
74
|
### Description template
|
|
22
75
|
|
|
23
76
|
Every operation `description` MUST be at least 150 characters and follow this structure:
|
|
@@ -53,6 +106,90 @@ description:
|
|
|
53
106
|
|
|
54
107
|
Use `defineOperation()` when an operation is large enough to live beside helper functions or in a separate module. It preserves the same type inference as inline `defineProvider()` operations and can be placed directly in the provider `operations` map. `defineProvider()` accepts Zod and Standard Schema v1-compatible schemas. If config validation fails, the SDK names the field to fix, for example `runtime`, `auth.mode`, `operations.<id>.handler`, or `operations.<id>.fixtures.response`.
|
|
55
108
|
|
|
109
|
+
### Replay-safe fixtures
|
|
110
|
+
|
|
111
|
+
Keep public operation schemas strict: date fields should accept absolute dates,
|
|
112
|
+
not relative tokens. Inside `fixtures.request` only, the SDK resolves `+Nd` and
|
|
113
|
+
`+Nd:YYYYMMDD` (1–365 days ahead) before import-time schema validation and
|
|
114
|
+
stores the resolved request in provider metadata. Health-check case inputs use
|
|
115
|
+
the same resolver when a probe runs. The default calendar is **KST**, including
|
|
116
|
+
the 15:00–23:59 UTC window when KST is already on the next day.
|
|
117
|
+
|
|
118
|
+
`fixtures.recordedAt` is the KST `YYYY-MM-DD` date when the response evidence
|
|
119
|
+
was captured. It must be a real, non-future calendar date. Response date fields
|
|
120
|
+
are expected to align with `recordedAt`, not with the newly resolved request;
|
|
121
|
+
this permits stable recorded evidence alongside a replay-safe request.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
const FlightInput = z.object({
|
|
125
|
+
departureDate: z.string().date(), // public calls remain absolute-date only
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const searchFlights = {
|
|
129
|
+
input: FlightInput,
|
|
130
|
+
output: FlightOutput,
|
|
131
|
+
async handler(ctx, input) {
|
|
132
|
+
return fetchAndNormalizeFlights(ctx, input);
|
|
133
|
+
},
|
|
134
|
+
fixtures: {
|
|
135
|
+
request: { departureDate: "+45d" },
|
|
136
|
+
response: recordedFlightResponse, // dates reflect the capture below
|
|
137
|
+
recordedAt: "2026-07-15",
|
|
138
|
+
},
|
|
139
|
+
healthCheckUnsupported: { reason: "Upstream search is cost-bearing." },
|
|
140
|
+
};
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
For code that explicitly calls the shared resolver, omit the third argument to
|
|
144
|
+
use KST or pass `"UTC"` deliberately:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { resolveHealthCheckInputDateTokens } from "@apifuse/provider-sdk/server";
|
|
148
|
+
|
|
149
|
+
const kstInput = resolveHealthCheckInputDateTokens({ date: "+45d" });
|
|
150
|
+
const utcInput = resolveHealthCheckInputDateTokens({ date: "+45d" }, new Date(), "UTC");
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Do not re-resolve a health assertion's dates in UTC when its case input used the
|
|
154
|
+
default KST calendar.
|
|
155
|
+
|
|
156
|
+
### Real-handler E2E in standard tests
|
|
157
|
+
|
|
158
|
+
`runStandardTests(provider)` validates declarations and fixtures but reports a
|
|
159
|
+
per-operation warning because it has no handler E2E coverage. Opt in with an
|
|
160
|
+
`upstreamStub`: the runner calls each fixture-backed real handler with its
|
|
161
|
+
already-resolved fixture request, routes ProviderContext upstream transports to
|
|
162
|
+
the stub, and validates the result against the output schema. It never compares
|
|
163
|
+
the result to the recorded response because that evidence belongs to
|
|
164
|
+
`recordedAt`.
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { runStandardTests } from "@apifuse/provider-sdk/testing";
|
|
168
|
+
import provider from "../index.js";
|
|
169
|
+
|
|
170
|
+
runStandardTests(provider, {
|
|
171
|
+
upstreamStub: ({ transport, method, url }) => {
|
|
172
|
+
if (
|
|
173
|
+
transport === "http" &&
|
|
174
|
+
method === "GET" &&
|
|
175
|
+
url === "https://api.example.test/flights"
|
|
176
|
+
) {
|
|
177
|
+
return Response.json({ flights: [{ id: "fixture-flight" }] });
|
|
178
|
+
}
|
|
179
|
+
return undefined; // fails the test: live-network passthrough is forbidden
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The stub also identifies `stealth`, `browser`, and `native` interactions. Return
|
|
185
|
+
a Web `Response` or `{ status, headers, body }`; an unmatched call fails with
|
|
186
|
+
the operation, transport, and method named in the error. Browser handlers expose
|
|
187
|
+
method-level calls such as `goto`, `evaluate`, and `locator.click`, so provide a
|
|
188
|
+
canned result for each method the handler uses. Native connections similarly
|
|
189
|
+
identify `connectTcp`/`connectTls` and subsequent `write` calls. Direct global
|
|
190
|
+
`fetch` or socket usage is outside this ProviderContext seam and should not be
|
|
191
|
+
used by provider handlers.
|
|
192
|
+
|
|
56
193
|
### Health assertion context
|
|
57
194
|
|
|
58
195
|
`healthCheck.cases[].assertions` receives a `HealthCheckAssertionContext` with
|
|
@@ -228,7 +365,7 @@ export default defineProvider({
|
|
|
228
365
|
```
|
|
229
366
|
<!-- @magic-end:sample -->
|
|
230
367
|
|
|
231
|
-
The journey runner supplies `ctx.gateway`, `ctx.sms.waitForOtp()`, `ctx.journal.sideEffect()`, `ctx.state`, and `ctx.event.operation()` to the
|
|
368
|
+
The journey runner supplies `ctx.gateway`, `ctx.sms.waitForOtp()`, `ctx.journal.sideEffect()`, `ctx.state`, and `ctx.event.operation()` to the required journey `run` function. Provider authors should keep `run` small: call the covered operations in step order, stop at the declared safe boundary, and let the generated health metadata carry schedule, timeout, required secret, and SMS matcher information to the health monitor.
|
|
232
369
|
|
|
233
370
|
For authenticated journeys, open a fresh connection inside `run` with `ctx.gateway.connect({ input: { ... } })`, execute covered operations with the returned `connectionId`, and disconnect in a `finally` block. Do not require or store long-lived `HEALTH_MONITOR_*_CONNECTION_ID` secrets; those stale connection IDs can hide broken login ceremonies.
|
|
234
371
|
|
|
@@ -256,6 +393,200 @@ External contributors are expected to submit standalone Provider source plus:
|
|
|
256
393
|
Maintainers own monorepo import under `providers/<id>/`, registry generation,
|
|
257
394
|
deployment projection checks, and release workflows.
|
|
258
395
|
|
|
396
|
+
### Error responses
|
|
397
|
+
|
|
398
|
+
Provider-server failures use a stable public envelope:
|
|
399
|
+
|
|
400
|
+
```json
|
|
401
|
+
{
|
|
402
|
+
"error": {
|
|
403
|
+
"code": "UPSTREAM_ERROR",
|
|
404
|
+
"message": "The upstream service failed",
|
|
405
|
+
"requestId": "req_123",
|
|
406
|
+
"retryable": true,
|
|
407
|
+
"details": { "providerReason": "temporarily_unavailable" }
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
`retryable` is always present on responses emitted by the current SDK. Set
|
|
413
|
+
`retryable` in the `ProviderError` options when the provider knows the answer;
|
|
414
|
+
an explicit `true` or `false` wins over the matching operation declaration and
|
|
415
|
+
SDK derivation. When it is omitted, `operations.<id>.docs.errorCodes[].retryable`
|
|
416
|
+
is used for a matching provider-owned code, followed by SDK derivation (which
|
|
417
|
+
defaults ordinary `ProviderError` values to `false`). During stateful rolling
|
|
418
|
+
upgrades, the forwarding client also accepts an older owner response that omits
|
|
419
|
+
`retryable` and treats it as `false` without loosening the emitted response
|
|
420
|
+
contract. Existing optional `fix` guidance is also preserved when a
|
|
421
|
+
`ProviderError` supplies it.
|
|
422
|
+
|
|
423
|
+
`details` belongs exclusively to the provider. The server passes
|
|
424
|
+
`ProviderError.options.details` through verbatim, including strings and arrays,
|
|
425
|
+
and never merges, overwrites, or wraps it. Do not put SDK taxonomy fields there.
|
|
426
|
+
SDK-owned validation and masked-internal-error paths retain their own diagnostic
|
|
427
|
+
details.
|
|
428
|
+
|
|
429
|
+
SDK observability is emitted separately in the
|
|
430
|
+
`X-ApiFuse-Error-Observability` response header as compact, single-line JSON:
|
|
431
|
+
|
|
432
|
+
```json
|
|
433
|
+
{"category":"upstream_http","taxonomyVersion":"2026-05-26","retryable":true,"upstreamStatus":502}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Treat this header as telemetry, not as provider-controlled public error detail.
|
|
437
|
+
Its category, taxonomy version, retryability, and optional upstream status match
|
|
438
|
+
the structured `provider_request_failed` log event.
|
|
439
|
+
|
|
440
|
+
Declare provider-owned operation failures next to their documentation. The
|
|
441
|
+
server builds a lookup once at startup and applies it to failures from that
|
|
442
|
+
operation:
|
|
443
|
+
|
|
444
|
+
```ts
|
|
445
|
+
docs: {
|
|
446
|
+
errorCodes: [{
|
|
447
|
+
code: "UPSTREAM_SCHEMA_ERROR",
|
|
448
|
+
status: 502,
|
|
449
|
+
retryable: true,
|
|
450
|
+
description: "The upstream response no longer matches its schema.",
|
|
451
|
+
}],
|
|
452
|
+
},
|
|
453
|
+
handler: async () => {
|
|
454
|
+
throw new ProviderError("Upstream schema changed", {
|
|
455
|
+
code: "UPSTREAM_SCHEMA_ERROR",
|
|
456
|
+
});
|
|
457
|
+
},
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
`defineProvider` accepts only statuses the server can emit: 400, 401, 404, 429,
|
|
461
|
+
500, 502, 503, and 504. Invalid declared statuses fail provider definition,
|
|
462
|
+
not a live request. Status selection uses this order:
|
|
463
|
+
|
|
464
|
+
1. SDK-owned errors retain SDK status semantics. Operation declarations cannot
|
|
465
|
+
override SDK-owned codes, stateful-forwarding failures, Zod/deadline errors,
|
|
466
|
+
or `TransportError` values.
|
|
467
|
+
2. A matching operation `errorCodes` entry with `status` supplies the status.
|
|
468
|
+
This slot applies to `ValidationError` as well as ordinary `ProviderError`.
|
|
469
|
+
3. The registered mappings below apply.
|
|
470
|
+
4. Existing fallbacks apply: `TransportError` 502/504, unregistered input
|
|
471
|
+
`ValidationError` 400 (output validation 500), and other unregistered
|
|
472
|
+
`ProviderError` values 500.
|
|
473
|
+
|
|
474
|
+
The registered mappings are:
|
|
475
|
+
|
|
476
|
+
| Error code or fallback | HTTP status |
|
|
477
|
+
| --- | ---: |
|
|
478
|
+
| `AUTH_REQUIRED`, `reauth_required` | 401 |
|
|
479
|
+
| `MISSING_SECRET` | 400 |
|
|
480
|
+
| `NOT_FOUND`, `not_found`, `NO_DATA` | 404 |
|
|
481
|
+
| `RATE_LIMITED`, `UPSTREAM_RATE_LIMIT`, `LIMITED_NUMBER_OF_SERVICE_REQUESTS_EXCEEDS_ERROR` | 429 |
|
|
482
|
+
| `UPSTREAM_ERROR`, `BLOCKED` | 502 |
|
|
483
|
+
| `STT_UNAVAILABLE`, `UNSUPPORTED_STT_BACKEND`, `STATEFUL_FORWARDING_REPLAY_CACHE_FULL` | 503 |
|
|
484
|
+
| Unregistered input `ValidationError` code | 400 |
|
|
485
|
+
| Other unregistered `ProviderError` code | 500 |
|
|
486
|
+
|
|
487
|
+
An unregistered non-validation `ProviderError` code returns HTTP 500 and emits
|
|
488
|
+
the greppable `unregistered_provider_error_code` signal with the code in the
|
|
489
|
+
structured failure log. A matching operation declaration, including one that
|
|
490
|
+
omits `status`, makes the code registered for this signal and may independently
|
|
491
|
+
supply `retryable`. The HTTP 400 `ValidationError` behavior is only the fallback
|
|
492
|
+
when neither an operation status nor a registered mapping applies.
|
|
493
|
+
|
|
494
|
+
Throw the domain `ProviderError` directly. Subclassing or wrapping it as a
|
|
495
|
+
`TransportError` solely to preserve a 5xx response is obsolete; declare the
|
|
496
|
+
domain code's `status` instead. Genuine `TransportError` values remain
|
|
497
|
+
SDK-owned and keep their 502/504 mapping.
|
|
498
|
+
|
|
499
|
+
### Declared secrets are SDK-enforced
|
|
500
|
+
|
|
501
|
+
Environment/secret presence validation is single-sourced in the SDK. Declare
|
|
502
|
+
every env secret the provider needs in `defineProvider`:
|
|
503
|
+
|
|
504
|
+
```ts
|
|
505
|
+
secrets: [
|
|
506
|
+
{
|
|
507
|
+
name: "APIFUSE__PROVIDER__MY_PROVIDER__API_KEY",
|
|
508
|
+
required: true,
|
|
509
|
+
description: "Upstream API key from the vendor portal",
|
|
510
|
+
},
|
|
511
|
+
],
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
The runtime validates every `required: true` declaration before any operation
|
|
515
|
+
handler or auth-flow handler (except `abort`) runs. When a required secret is
|
|
516
|
+
unset or whitespace-only, the invocation fails with the canonical structured
|
|
517
|
+
error — code `MISSING_SECRET`, HTTP 400, top-level `retryable: false`, and a
|
|
518
|
+
`fix` naming every missing secret — across `/v1/{operation}`, self-test probes,
|
|
519
|
+
`apifuse perf`, and `apifuse record`. Its error-observability header carries the
|
|
520
|
+
`credential_unavailable` category. The server also emits a
|
|
521
|
+
`provider_secrets_missing` warn log at boot so unprovisioned deployments are
|
|
522
|
+
visible immediately without crashing the pod.
|
|
523
|
+
|
|
524
|
+
Provider-local presence re-validation is **deprecated**: do not write
|
|
525
|
+
`requireServiceKey`/`requireApiKey`-style guards that re-check `ctx.env.get()`
|
|
526
|
+
and throw a hand-rolled `CONFIGURATION_ERROR`/`MISSING_SECRET`. Those guards
|
|
527
|
+
are dead weight (the SDK gate runs first) and historically diverged into
|
|
528
|
+
inconsistent error shapes. The `sdk-owned-secret-presence` submit-check rule
|
|
529
|
+
flags them at warn level; acknowledge a deliberate exception with
|
|
530
|
+
`// @apifuse-allow sdk-owned-secret-presence: <reason>`.
|
|
531
|
+
|
|
532
|
+
```ts
|
|
533
|
+
// Before (deprecated): provider-local double validation
|
|
534
|
+
function requireServiceKey(ctx: ProviderContext): string {
|
|
535
|
+
const value = ctx.env.get(SERVICE_KEY_ENV);
|
|
536
|
+
if (!value?.trim()) {
|
|
537
|
+
throw new ProviderError(`Missing required provider secret: ${SERVICE_KEY_ENV}`, {
|
|
538
|
+
code: "CONFIGURATION_ERROR",
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
return value;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// After: declare { name: SERVICE_KEY_ENV, required: true } and read directly.
|
|
545
|
+
const serviceKey = ctx.env.get(SERVICE_KEY_ENV);
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
Note the asymmetry: the gate treats whitespace-only values as missing, but
|
|
549
|
+
`ctx.env.get()` still returns the raw value to handlers — trim at the point of
|
|
550
|
+
use if the upstream is whitespace-sensitive.
|
|
551
|
+
|
|
552
|
+
### Credentials forced into query parameters
|
|
553
|
+
|
|
554
|
+
Prefer an authorization header or request body whenever the upstream supports
|
|
555
|
+
one. When the upstream requires a credential in the URL query (for example
|
|
556
|
+
`serviceKey`, `confmKey`, or `crtfc_key`), use `sensitiveParams`:
|
|
557
|
+
|
|
558
|
+
```ts
|
|
559
|
+
const response = await ctx.http.get("/openapi/lookup", {
|
|
560
|
+
params: { pageNo: 1, numOfRows: 100 },
|
|
561
|
+
sensitiveParams: {
|
|
562
|
+
serviceKey: ctx.env.get("APIFUSE__PROVIDER__EXAMPLE__SERVICE_KEY")!,
|
|
563
|
+
},
|
|
564
|
+
});
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
`sensitiveParams` is merged into the outgoing query like `params`, while its
|
|
568
|
+
values are redacted from SDK transport errors, traces, and `apifuse record`
|
|
569
|
+
fixtures. Do not put query credentials in `params`, and do not hand-build a URL
|
|
570
|
+
containing a key; those paths cannot declare which query values are secret.
|
|
571
|
+
|
|
572
|
+
#### Residual risks
|
|
573
|
+
|
|
574
|
+
Redaction is unconditional in structural positions (declared query keys and
|
|
575
|
+
exact scalar fixture/error fields) for values of every length. In unstructured
|
|
576
|
+
free text, values of four or more characters are replaced as substrings; shorter
|
|
577
|
+
values are replaced only at token boundaries to avoid corrupting unrelated text
|
|
578
|
+
(for example, a secret `api` must not rewrite `rapid`). The residual risk is that
|
|
579
|
+
a sub-four-character secret embedded directly inside a larger alphanumeric token
|
|
580
|
+
can remain in free text. Prefer a higher-entropy credential, or a header/body
|
|
581
|
+
credential channel, whenever the upstream permits it. An empty
|
|
582
|
+
`sensitiveParams: {}` is treated exactly as if the option were omitted.
|
|
583
|
+
|
|
584
|
+
For `session.redirects.run()`, returned hop URLs are diagnostic metadata and
|
|
585
|
+
therefore keep declared query values and common response-only credential keys
|
|
586
|
+
redacted. If a login flow must consume a rotated credential from `Location`,
|
|
587
|
+
inspect it inside `stopWhen`; that callback receives the real hop while callback
|
|
588
|
+
failures are sanitized before propagation.
|
|
589
|
+
|
|
259
590
|
### Public local debugging checklist
|
|
260
591
|
|
|
261
592
|
- Operation smoke requests use the provider server envelope:
|
|
@@ -403,14 +734,171 @@ const credentialsAuth = defineCredentialsAuth({
|
|
|
403
734
|
request's `context`.
|
|
404
735
|
- Stealth/browser providers may require local runtime setup outside Provider code:
|
|
405
736
|
keep access-sensitive operations on `ctx.stealth.fetch()` with an SDK stealth
|
|
406
|
-
`profile`; the TypeScript runtime uses `
|
|
737
|
+
`profile`; the TypeScript runtime uses `wreq-js` behind that interface, so do
|
|
407
738
|
not add per-operation JA3, HTTP/2 SETTINGS, or pseudo-header tuning. `ctx.stealth`
|
|
408
|
-
supports Chrome
|
|
409
|
-
|
|
410
|
-
(`nodriver` is Python-runtime only);
|
|
739
|
+
supports Chrome, Firefox, and Safari profiles; use `ctx.browser` when a
|
|
740
|
+
Provider needs real browser execution. TypeScript browser Providers use
|
|
741
|
+
`browser.engine: "playwright-stealth"` (`nodriver` is Python-runtime only);
|
|
742
|
+
install local browser assets with
|
|
411
743
|
`bunx playwright install chromium`, or set
|
|
412
744
|
`APIFUSE__CDP_POOL__URL` for remote browser debugging.
|
|
413
745
|
|
|
746
|
+
### Native gateway adapter migration
|
|
747
|
+
|
|
748
|
+
Native proxy resolution supports both HTTP CONNECT and SOCKS5 without
|
|
749
|
+
terminating origin TLS. The built-in vendor order follows `proxy.providers`
|
|
750
|
+
exactly: `smartproxy` means the `api.smartproxy.org` allocation vendor (raw
|
|
751
|
+
`ip:port` endpoints), while `nodemaven` is the credentialed gateway. It is not
|
|
752
|
+
the company formerly called Smartproxy; that separate company is represented
|
|
753
|
+
by the deprecated `decodo` name.
|
|
754
|
+
|
|
755
|
+
Custom `gatewaySynthesizers` must now accept the selected `protocol` and the
|
|
756
|
+
injected `credentials` resolver on `NativeGatewayProxySynthesisInput`.
|
|
757
|
+
Synthesizers may be async because allocation vendors perform network I/O. They
|
|
758
|
+
may return a proxy, `undefined` when they do not implement the offered vendor,
|
|
759
|
+
or `{ kind: "skipped", reason }` so an exhausted required chain can explain an
|
|
760
|
+
absent credential, unsupported protocol, or allocation failure. Accordingly,
|
|
761
|
+
`resolveNativeGatewayProxy(...)` must now be awaited.
|
|
762
|
+
|
|
763
|
+
Callers that do not supply custom synthesizers or credentials keep env-backed
|
|
764
|
+
behavior. Hosts that already have an allowlisted `EnvContext` can inject it
|
|
765
|
+
explicitly, and vault-backed or per-tenant hosts can supply their own resolver:
|
|
766
|
+
|
|
767
|
+
```ts
|
|
768
|
+
import {
|
|
769
|
+
createEnvVendorCredentialResolver,
|
|
770
|
+
createNativeNetworkClient,
|
|
771
|
+
} from "@apifuse/provider-sdk";
|
|
772
|
+
|
|
773
|
+
const network = createNativeNetworkClient({
|
|
774
|
+
proxyPolicy: { mode: "required", providers: ["smartproxy", "nodemaven"] },
|
|
775
|
+
credentials: createEnvVendorCredentialResolver(ctx.env),
|
|
776
|
+
// Optional advanced override; omit for each vendor's default.
|
|
777
|
+
proxyProtocol: "socks5",
|
|
778
|
+
});
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
Never include credential values in adapter skip messages or thrown errors. The
|
|
782
|
+
SDK redacts built-in proxy URL userinfo, CONNECT authentication, and allocator
|
|
783
|
+
causes, but a custom adapter remains responsible for not publishing secrets in
|
|
784
|
+
its own diagnostics.
|
|
785
|
+
|
|
786
|
+
### Limiting stealth response bodies
|
|
787
|
+
|
|
788
|
+
Set `maxBodyBytes` on `ctx.stealth.fetch()` or `session.redirects.run()` when an
|
|
789
|
+
upstream response has a known safe maximum. The limit is opt-in and counts
|
|
790
|
+
decoded bytes as `wreq-js` streams them. It applies to every redirect hop, uses a
|
|
791
|
+
parseable `Content-Length` for an early rejection, and still enforces the limit
|
|
792
|
+
incrementally when the header is absent or inaccurate. Exceeding the limit
|
|
793
|
+
aborts the response and throws a non-retryable `TransportError` with code
|
|
794
|
+
`response_too_large`.
|
|
795
|
+
|
|
796
|
+
Pass the limit to the transport instead of checking `Content-Length` in provider
|
|
797
|
+
code:
|
|
798
|
+
|
|
799
|
+
```ts
|
|
800
|
+
const response = await ctx.stealth.fetch("/api/search", {
|
|
801
|
+
params: { query: input.query },
|
|
802
|
+
maxBodyBytes: 2 * 1024 * 1024,
|
|
803
|
+
})
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
### Persisting stealth session cookies
|
|
807
|
+
|
|
808
|
+
Persist `session.cookies.serialize()` as JSON when an authenticated session must
|
|
809
|
+
survive a restart or move to another replica. The returned
|
|
810
|
+
`StealthCookieStoreV1` has an explicit version and retains every cookie together
|
|
811
|
+
with its Domain, Path, Secure, expiry, host-only, and other cookie attributes.
|
|
812
|
+
Restore it with `session.cookies.deserialize()`. Unsupported future versions
|
|
813
|
+
fail explicitly instead of being accepted as a partial cookie jar.
|
|
814
|
+
|
|
815
|
+
Credential values are strings, so stringify the store at the credential
|
|
816
|
+
boundary and parse it when rebuilding the session:
|
|
817
|
+
|
|
818
|
+
```ts
|
|
819
|
+
// After login (including any redirects across sibling hosts):
|
|
820
|
+
const result = await session.redirects.run({ url: loginUrl });
|
|
821
|
+
return {
|
|
822
|
+
credential: {
|
|
823
|
+
cookieStore: JSON.stringify(result.cookieStore),
|
|
824
|
+
},
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
// In a later operation or replica:
|
|
828
|
+
const persisted = ctx.credential.get("cookieStore");
|
|
829
|
+
if (persisted) {
|
|
830
|
+
session.cookies.deserialize(JSON.parse(persisted));
|
|
831
|
+
}
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
`snapshot()` and `restore()` remain only for backward compatibility with flat
|
|
835
|
+
`Record<string, string>` credentials. `snapshot()` enumerates cookies across all
|
|
836
|
+
hosts and paths, but the flat shape is inherently lossy: duplicate names
|
|
837
|
+
collapse and Domain, Path, Secure, expiry, and host-only attributes cannot be
|
|
838
|
+
represented. `restore()` therefore recreates host-only `Path=/` cookies on the
|
|
839
|
+
session base origin. Do not use the flat form for new persistence code. Cookie
|
|
840
|
+
headers remain origin-filtered: use `toHeader(url)` for a particular request and
|
|
841
|
+
never build a request header from serialized or snapshotted persistence data.
|
|
842
|
+
|
|
843
|
+
### Recording and replaying streaming responses
|
|
844
|
+
|
|
845
|
+
`apifuse record` passes responses returned by `ctx.http.stream()` directly to the operation
|
|
846
|
+
handler while incrementally capturing a bounded preview. If the handler returns or cancels its
|
|
847
|
+
reader before EOF, the recorder drains the retained upstream reader before writing a JSON evidence
|
|
848
|
+
record to `__fixtures__/raw.json`. The record contains the status, success
|
|
849
|
+
flag, `content-type`/`content-length`/`content-disposition` headers when present, the
|
|
850
|
+
full body SHA-256 and byte count, and a base64 preview up to the configured stream preview limit.
|
|
851
|
+
Textual previews are decoded and passed through the fixture sanitizer before base64 encoding.
|
|
852
|
+
Classification uses both the declared content type and the preview bytes, so missing or incorrect
|
|
853
|
+
content-type headers do not bypass sanitization. PEM private-key blocks and long high-entropy
|
|
854
|
+
tokens in otherwise unstructured text are redacted as well. If the full preview is not valid UTF-8,
|
|
855
|
+
the entire lossy-decoded preview is scanned and matching decodable byte windows are sanitized. Only a
|
|
856
|
+
magic-number-confirmed binary preview with no textual-secret pattern anywhere in the preview bypasses
|
|
857
|
+
sanitization; other undecodable data fails closed.
|
|
858
|
+
Sanitized previews carry `preview_sanitized: true`, plus a
|
|
859
|
+
`preview_redaction_reason` when capture had to fail closed. The original hash and byte count always
|
|
860
|
+
describe upstream bytes, not a sanitized preview.
|
|
861
|
+
|
|
862
|
+
Each record includes query-free request provenance (`method`, `path`, and a one-based stream call
|
|
863
|
+
ordinal). Provenance never stores the origin, URL userinfo, query, or fragment. Every retained path
|
|
864
|
+
segment is scrubbed before persistence: credential-key segments, values following those keys,
|
|
865
|
+
known token shapes, and long high-entropy opaque segments become `[REDACTED]`. If an operation
|
|
866
|
+
opens multiple streams, the recorder finalizes every retained reader and
|
|
867
|
+
writes all evidence records in stream call order. Stream invocations use a tagged capture envelope
|
|
868
|
+
whose items distinguish stream evidence from ordinary JSON responses. Evidence-only snapshot replay
|
|
869
|
+
consumes that exact call order and fails immediately when evidence is exhausted or a call kind is
|
|
870
|
+
reordered. When request provenance is present, replay also rejects method or path changes (relative
|
|
871
|
+
URLs are resolved against the recorded path prefix); ordinals remain diagnostic and are not matched.
|
|
872
|
+
Appended fixtures replay a stream envelope only when it is the latest invocation. SSE
|
|
873
|
+
recording remains unsupported and fails explicitly instead of retaining an unrelated earlier
|
|
874
|
+
response.
|
|
875
|
+
|
|
876
|
+
#### Residual risks
|
|
877
|
+
|
|
878
|
+
- Credential-path sanitization decodes each URL path segment once. Double-encoded separators or
|
|
879
|
+
values such as `%252F` are not decoded recursively, so they can conceal a credential-shaped
|
|
880
|
+
segment from the recorder. This single-pass policy keeps path handling deterministic and avoids
|
|
881
|
+
interpreting ambiguous or intentionally layered encodings differently from the upstream. Never
|
|
882
|
+
place credentials in URL paths, and review recorded provenance before committing fixtures.
|
|
883
|
+
- Primitive strings embedded in prose are redacted only when they match the current PEM,
|
|
884
|
+
credential-assignment, known-token, or entropy heuristics. Other secret formats can remain because
|
|
885
|
+
blanket redaction of ordinary strings would destroy useful fixture content and create broad false
|
|
886
|
+
positives. Keep secrets under credential-named structured fields where possible and manually
|
|
887
|
+
inspect sanitized fixture text before committing it.
|
|
888
|
+
|
|
889
|
+
Stream fixture replay in `runStandardTests(..., { snapshot: true })` is evidence-only:
|
|
890
|
+
`ctx.http.stream()` returns a usable stream containing exactly the recorded preview,
|
|
891
|
+
not a fabricated full body. The replay response also carries runtime metadata
|
|
892
|
+
`evidence_only: true`, `body_sha256`, `body_bytes`, and the optional preview sanitization fields
|
|
893
|
+
for assertions about the original capture. Do not assert that the replay body hashes to
|
|
894
|
+
`body_sha256` when `body_bytes`
|
|
895
|
+
exceeds the decoded preview length or `preview_sanitized` is present; use the metadata for
|
|
896
|
+
full-body integrity and limit body-content assertions to the preview.
|
|
897
|
+
|
|
898
|
+
Golden snapshot suites can set `requireSnapshot: true` so a missing committed snapshot fails instead
|
|
899
|
+
of being created implicitly. Regenerate intentional changes with
|
|
900
|
+
`bun test --update-snapshots`; review and commit the resulting `transform.snap.json` file.
|
|
901
|
+
|
|
414
902
|
### Running the pre-submission report
|
|
415
903
|
|
|
416
904
|
```bash
|