@apifuse/provider-sdk 2.2.0-beta.7 → 2.2.0-beta.70
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 +943 -51
- package/CHANGELOG.md +273 -1
- package/LICENSE +21 -0
- package/README.md +117 -29
- package/SUBMISSION.md +3 -2
- package/bin/apifuse-check.ts +293 -13
- package/bin/apifuse-dev.ts +42 -19
- package/bin/apifuse-migrate-deployment.ts +200 -0
- package/bin/apifuse-migrate-operation-declaration.ts +55 -0
- package/bin/apifuse-migrate-shape.ts +202 -0
- package/bin/apifuse-pack-check.ts +19 -6
- package/bin/apifuse-pack-smoke.ts +57 -2
- package/bin/apifuse-pack-types.ts +736 -38
- package/bin/apifuse-perf.ts +59 -23
- package/bin/apifuse-record.ts +923 -94
- package/bin/apifuse-submit-check.ts +2313 -281
- package/bin/apifuse-test.ts +3 -1
- package/bin/submit-check-delimited-text.ts +50 -0
- package/dist/auth-turn/index.d.ts +3 -3
- package/dist/auth-turn/index.js +1 -1
- package/dist/auth.d.ts +14 -0
- package/dist/auth.js +67 -0
- package/dist/ceremonies/index.d.ts +16 -0
- package/dist/ceremonies/index.js +141 -36
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +30 -0
- package/dist/cli/create.d.ts +1 -1
- package/dist/cli/create.js +8 -2
- package/dist/cli/deployment-intent.d.ts +178 -0
- package/dist/cli/deployment-intent.js +277 -0
- package/dist/cli/local-connection.d.ts +111 -0
- package/dist/cli/local-connection.js +151 -0
- package/dist/cli/migrate-deployment-intent.d.ts +132 -0
- package/dist/cli/migrate-deployment-intent.js +1552 -0
- package/dist/cli/migrate-operation-declaration.d.ts +70 -0
- package/dist/cli/migrate-operation-declaration.js +2206 -0
- package/dist/cli/migrate-operation-shape.d.ts +44 -0
- package/dist/cli/migrate-operation-shape.js +113 -0
- package/dist/cli/migrate-provider-shape.d.ts +56 -0
- package/dist/cli/migrate-provider-shape.js +580 -0
- package/dist/cli/resolver-runtime.d.ts +21 -0
- package/dist/cli/resolver-runtime.js +31 -0
- package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +141 -23
- package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
- package/dist/cli/templates/provider/README.md.tpl +41 -19
- package/dist/cli/templates/provider/index.ts.tpl +6 -3
- package/dist/cli/templates/provider/operations/ping.ts.tpl +4 -1
- package/dist/cli/templates/provider/provider.json.tpl +6 -0
- package/dist/config/loader.d.ts +202 -16
- package/dist/config/loader.js +491 -141
- package/dist/contract-serialization.js +4 -8
- package/dist/contract-types.d.ts +12 -5
- package/dist/contract.js +24 -10
- package/dist/declaration-validation.d.ts +34 -0
- package/dist/declaration-validation.js +305 -0
- package/dist/define.d.ts +71 -29
- package/dist/define.js +807 -69
- package/dist/dev.d.ts +5 -0
- package/dist/dev.js +8 -1
- package/dist/engine.d.ts +129 -0
- package/dist/engine.js +209 -0
- package/dist/error-observability.d.ts +7 -0
- package/dist/error-observability.js +61 -0
- package/dist/error-resolution.d.ts +18 -0
- package/dist/error-resolution.js +243 -0
- package/dist/errors.d.ts +74 -0
- package/dist/errors.js +99 -0
- package/dist/fixture-sanitization.d.ts +28 -0
- package/dist/fixture-sanitization.js +371 -0
- package/dist/handle-meta.d.ts +90 -0
- package/dist/handle-meta.js +73 -0
- package/dist/handle.d.ts +226 -0
- package/dist/handle.js +345 -0
- package/dist/health-scenario.d.ts +1901 -0
- package/dist/health-scenario.js +653 -0
- package/dist/i18n/catalog.js +21 -1
- package/dist/i18n/error-messages.d.ts +86 -0
- package/dist/i18n/error-messages.js +192 -0
- package/dist/i18n/index.d.ts +1 -0
- package/dist/i18n/index.js +1 -0
- package/dist/i18n/keys.d.ts +12 -0
- package/dist/i18n/keys.js +76 -5
- package/dist/i18n/operation-locale-namespace.d.ts +2 -0
- package/dist/i18n/operation-locale-namespace.js +7 -0
- package/dist/index.d.ts +40 -15
- package/dist/index.js +29 -11
- package/dist/lint.d.ts +43 -11
- package/dist/lint.js +2512 -177
- 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 +18 -8
- package/dist/provider.js +11 -6
- package/dist/runtime/auth-flow.d.ts +6 -1
- package/dist/runtime/auth-flow.js +8 -0
- package/dist/runtime/bounded-expiring-map.d.ts +21 -0
- package/dist/runtime/bounded-expiring-map.js +64 -0
- package/dist/runtime/browser-telemetry-binding.d.ts +5 -0
- package/dist/runtime/browser-telemetry-binding.js +83 -0
- package/dist/runtime/browser-telemetry.d.ts +76 -0
- package/dist/runtime/browser-telemetry.js +165 -0
- package/dist/runtime/browser.d.ts +4 -0
- package/dist/runtime/browser.js +688 -83
- package/dist/runtime/cache-telemetry.d.ts +71 -0
- package/dist/runtime/cache-telemetry.js +177 -0
- package/dist/runtime/cache.d.ts +3 -0
- package/dist/runtime/cache.js +245 -23
- package/dist/runtime/capability-response.d.ts +3 -0
- package/dist/runtime/capability-response.js +56 -0
- package/dist/runtime/capability-telemetry.d.ts +46 -0
- package/dist/runtime/capability-telemetry.js +68 -0
- package/dist/runtime/chrome149-header-order.d.ts +58 -0
- package/dist/runtime/chrome149-header-order.js +289 -0
- package/dist/runtime/diagnostic-env-values.d.ts +5 -0
- package/dist/runtime/diagnostic-env-values.js +57 -0
- package/dist/runtime/diagnostic-env.d.ts +14 -0
- package/dist/runtime/diagnostic-env.js +44 -0
- package/dist/runtime/diagnostic-redactor.d.ts +54 -0
- package/dist/runtime/diagnostic-redactor.js +571 -0
- package/dist/runtime/egress-lease.d.ts +65 -0
- package/dist/runtime/egress-lease.js +236 -0
- package/dist/runtime/engine-mode.d.ts +74 -0
- package/dist/runtime/engine-mode.js +152 -0
- package/dist/runtime/env.js +14 -1
- package/dist/runtime/executor.d.ts +3 -2
- package/dist/runtime/executor.js +23 -26
- package/dist/runtime/handle-wordlist.d.ts +43 -0
- package/dist/runtime/handle-wordlist.js +256 -0
- package/dist/runtime/handle.d.ts +71 -0
- package/dist/runtime/handle.js +908 -0
- package/dist/runtime/http-telemetry-binding.d.ts +10 -0
- package/dist/runtime/http-telemetry-binding.js +152 -0
- package/dist/runtime/http-telemetry-guard.d.ts +16 -0
- package/dist/runtime/http-telemetry-guard.js +202 -0
- package/dist/runtime/http-telemetry.d.ts +104 -0
- package/dist/runtime/http-telemetry.js +190 -0
- package/dist/runtime/http.d.ts +34 -0
- package/dist/runtime/http.js +680 -121
- package/dist/runtime/insights.js +2 -2
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +462 -19
- 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 +105 -0
- package/dist/runtime/native-network.js +1433 -0
- package/dist/runtime/native-telemetry.d.ts +142 -0
- package/dist/runtime/native-telemetry.js +231 -0
- package/dist/runtime/ocr-telemetry.d.ts +82 -0
- package/dist/runtime/ocr-telemetry.js +192 -0
- package/dist/runtime/ocr.d.ts +29 -0
- package/dist/runtime/ocr.js +468 -0
- package/dist/runtime/otlp.d.ts +71 -2
- package/dist/runtime/otlp.js +398 -16
- package/dist/runtime/provider.d.ts +7 -0
- package/dist/runtime/provider.js +14 -0
- package/dist/runtime/proxy-errors.js +6 -2
- package/dist/runtime/proxy-nodemaven.d.ts +56 -0
- package/dist/runtime/proxy-nodemaven.js +147 -0
- package/dist/runtime/proxy-retry-policy.d.ts +4 -1
- package/dist/runtime/proxy-retry-policy.js +11 -2
- package/dist/runtime/proxy-telemetry.d.ts +133 -3
- package/dist/runtime/proxy-telemetry.js +218 -70
- package/dist/runtime/redirect-hop-headers.d.ts +19 -0
- package/dist/runtime/redirect-hop-headers.js +58 -0
- package/dist/runtime/redirects.d.ts +61 -0
- package/dist/runtime/redirects.js +149 -0
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +4 -2
- package/dist/runtime/request-options.d.ts +75 -1
- package/dist/runtime/request-options.js +588 -0
- package/dist/runtime/request-telemetry.d.ts +70 -0
- package/dist/runtime/request-telemetry.js +229 -0
- package/dist/runtime/resolver-config.d.ts +13 -0
- package/dist/runtime/resolver-config.js +14 -0
- package/dist/runtime/resolver-public.d.ts +3 -0
- package/dist/runtime/resolver-public.js +1 -0
- package/dist/runtime/resolver-runtime-options.d.ts +14 -0
- package/dist/runtime/resolver-runtime-options.js +33 -0
- package/dist/runtime/resolver-shared.d.ts +24 -0
- package/dist/runtime/resolver-shared.js +69 -0
- package/dist/runtime/resolver-span-attributes.d.ts +34 -0
- package/dist/runtime/resolver-span-attributes.js +93 -0
- package/dist/runtime/resolver-telemetry.d.ts +135 -0
- package/dist/runtime/resolver-telemetry.js +204 -0
- package/dist/runtime/resolver-usage.d.ts +32 -0
- package/dist/runtime/resolver-usage.js +54 -0
- package/dist/runtime/resolver-vendors/bindings.d.ts +53 -0
- package/dist/runtime/resolver-vendors/bindings.js +45 -0
- package/dist/runtime/resolver-vendors/browser.d.ts +24 -0
- package/dist/runtime/resolver-vendors/browser.js +423 -0
- package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
- package/dist/runtime/resolver-vendors/capsolver.js +544 -0
- package/dist/runtime/resolver-vendors/hosts.d.ts +23 -0
- package/dist/runtime/resolver-vendors/hosts.js +68 -0
- package/dist/runtime/resolver-vendors/hypersolutions.d.ts +27 -0
- package/dist/runtime/resolver-vendors/hypersolutions.js +369 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
- package/dist/runtime/resolver-vendors/twocaptcha.js +425 -0
- package/dist/runtime/resolver-vendors/types.d.ts +125 -0
- package/dist/runtime/resolver-vendors/types.js +106 -0
- package/dist/runtime/resolver.d.ts +79 -0
- package/dist/runtime/resolver.js +1094 -0
- package/dist/runtime/state-telemetry.d.ts +79 -0
- package/dist/runtime/state-telemetry.js +194 -0
- package/dist/runtime/state.d.ts +10 -1
- package/dist/runtime/state.js +322 -82
- package/dist/runtime/stealth-akamai-sbsd.d.ts +54 -0
- package/dist/runtime/stealth-akamai-sbsd.js +142 -0
- package/dist/runtime/stealth-cookies.d.ts +24 -0
- package/dist/runtime/stealth-cookies.js +131 -0
- package/dist/runtime/stealth-owned-headers.d.ts +39 -0
- package/dist/runtime/stealth-owned-headers.js +57 -0
- package/dist/runtime/stealth-telemetry.d.ts +92 -0
- package/dist/runtime/stealth-telemetry.js +179 -0
- package/dist/runtime/stealth.d.ts +45 -6
- package/dist/runtime/stealth.js +1775 -320
- package/dist/runtime/stt-telemetry.d.ts +77 -0
- package/dist/runtime/stt-telemetry.js +176 -0
- package/dist/runtime/stt.js +18 -19
- package/dist/runtime/timeout.d.ts +5 -0
- package/dist/runtime/timeout.js +12 -0
- package/dist/runtime/trace-config.d.ts +12 -0
- package/dist/runtime/trace-config.js +62 -0
- package/dist/runtime/trace.d.ts +17 -0
- package/dist/runtime/trace.js +176 -25
- package/dist/runtime-boundary-lint.d.ts +137 -0
- package/dist/runtime-boundary-lint.js +590 -0
- package/dist/schema.d.ts +20 -0
- package/dist/schema.js +72 -2
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/error-observability.d.ts +1 -0
- package/dist/server/error-observability.js +1 -0
- package/dist/server/error-stack-frames.d.ts +35 -0
- package/dist/server/error-stack-frames.js +219 -0
- package/dist/server/index.d.ts +21 -2
- package/dist/server/index.js +11 -2
- package/dist/server/runtime-diagnostics.d.ts +165 -0
- package/dist/server/runtime-diagnostics.js +360 -0
- package/dist/server/sdk-version.d.ts +3 -0
- package/dist/server/sdk-version.js +19 -0
- 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.js +8 -33
- package/dist/server/self-test.d.ts +18 -3
- package/dist/server/self-test.js +139 -76
- package/dist/server/sensitive-values.d.ts +26 -0
- package/dist/server/sensitive-values.js +137 -0
- package/dist/server/serve-implementation.d.ts +285 -0
- package/dist/server/serve-implementation.js +3172 -0
- package/dist/server/serve.d.ts +1 -75
- package/dist/server/serve.js +1 -1170
- package/dist/server/state-backend-report.d.ts +70 -0
- package/dist/server/state-backend-report.js +113 -0
- package/dist/server/trace-output.d.ts +7 -0
- package/dist/server/trace-output.js +111 -0
- package/dist/server/types.d.ts +37 -5
- package/dist/server/types.js +34 -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 +237 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +35 -0
- package/dist/stateful/stateful-provider-runtime-context.js +61 -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 +15 -4
- package/dist/stealth/profiles.js +169 -211
- package/dist/stream-evidence.d.ts +74 -0
- package/dist/stream-evidence.js +785 -0
- package/dist/stream.js +7 -1
- package/dist/testing/index.d.ts +2 -1
- package/dist/testing/index.js +2 -1
- package/dist/testing/run.d.ts +32 -2
- package/dist/testing/run.js +509 -25
- package/dist/trace-sanitization.d.ts +6 -0
- package/dist/trace-sanitization.js +68 -0
- package/dist/types.d.ts +899 -210
- package/dist/types.js +1 -0
- package/dist/typescript-module.d.ts +39 -0
- package/dist/typescript-module.js +72 -0
- package/package.json +52 -6
- package/src/auth-turn/index.ts +1 -1
- package/src/auth.ts +118 -0
- package/src/ceremonies/index.ts +189 -46
- package/src/cli/commands.ts +37 -0
- package/src/cli/create.ts +8 -2
- package/src/cli/deployment-intent.ts +364 -0
- package/src/cli/local-connection.ts +221 -0
- package/src/cli/migrate-deployment-intent.ts +1908 -0
- package/src/cli/migrate-operation-declaration.ts +3026 -0
- package/src/cli/migrate-operation-shape.ts +184 -0
- package/src/cli/migrate-provider-shape.ts +778 -0
- package/src/cli/resolver-runtime.ts +72 -0
- package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +141 -23
- package/src/cli/templates/provider/Dockerfile.tpl +1 -1
- package/src/cli/templates/provider/README.md.tpl +41 -19
- package/src/cli/templates/provider/index.ts.tpl +6 -3
- package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
- package/src/cli/templates/provider/provider.json.tpl +6 -0
- package/src/config/loader.ts +774 -181
- package/src/contract-serialization.ts +5 -7
- package/src/contract-types.ts +12 -5
- package/src/contract.ts +24 -10
- package/src/declaration-validation.ts +376 -0
- package/src/define.ts +1105 -131
- package/src/dev.ts +13 -1
- package/src/engine.ts +390 -0
- package/src/error-observability.ts +64 -0
- package/src/error-resolution.ts +269 -0
- package/src/errors.ts +157 -0
- package/src/fixture-sanitization.ts +422 -0
- package/src/handle-meta.ts +143 -0
- package/src/handle.ts +694 -0
- package/src/health-scenario.ts +960 -0
- package/src/i18n/catalog.ts +23 -1
- package/src/i18n/error-messages.ts +255 -0
- package/src/i18n/index.ts +1 -0
- package/src/i18n/keys.ts +80 -6
- package/src/i18n/operation-locale-namespace.ts +10 -0
- package/src/index.ts +390 -23
- package/src/lint.ts +2975 -234
- 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 +207 -27
- package/src/runtime/auth-flow.ts +15 -0
- package/src/runtime/bounded-expiring-map.ts +67 -0
- package/src/runtime/browser-telemetry-binding.ts +92 -0
- package/src/runtime/browser-telemetry.ts +289 -0
- package/src/runtime/browser.ts +882 -99
- package/src/runtime/cache-telemetry.ts +247 -0
- package/src/runtime/cache.ts +258 -22
- package/src/runtime/capability-response.ts +53 -0
- package/src/runtime/capability-telemetry.ts +101 -0
- package/src/runtime/chrome149-header-order.ts +330 -0
- package/src/runtime/diagnostic-env-values.ts +56 -0
- package/src/runtime/diagnostic-env.ts +65 -0
- package/src/runtime/diagnostic-redactor.ts +668 -0
- package/src/runtime/egress-lease.ts +341 -0
- package/src/runtime/engine-mode.ts +233 -0
- package/src/runtime/env.ts +15 -1
- package/src/runtime/executor.ts +39 -32
- package/src/runtime/handle-wordlist.ts +266 -0
- package/src/runtime/handle.ts +1180 -0
- package/src/runtime/http-telemetry-binding.ts +162 -0
- package/src/runtime/http-telemetry-guard.ts +208 -0
- package/src/runtime/http-telemetry.ts +292 -0
- package/src/runtime/http.ts +896 -150
- package/src/runtime/insights.ts +2 -2
- package/src/runtime/instrumentation.ts +628 -28
- package/src/runtime/native-network-errors.ts +99 -0
- package/src/runtime/native-network.ts +1853 -0
- package/src/runtime/native-telemetry.ts +366 -0
- package/src/runtime/ocr-telemetry.ts +308 -0
- package/src/runtime/ocr.ts +564 -0
- package/src/runtime/otlp.ts +468 -21
- package/src/runtime/provider.ts +15 -0
- package/src/runtime/proxy-errors.ts +12 -4
- package/src/runtime/proxy-nodemaven.ts +222 -0
- package/src/runtime/proxy-retry-policy.ts +12 -1
- package/src/runtime/proxy-telemetry.ts +358 -103
- package/src/runtime/redirect-hop-headers.ts +71 -0
- package/src/runtime/redirects.ts +215 -0
- package/src/runtime/redis.ts +7 -2
- package/src/runtime/request-options.ts +728 -1
- package/src/runtime/request-telemetry.ts +375 -0
- package/src/runtime/resolver-config.ts +18 -0
- package/src/runtime/resolver-public.ts +44 -0
- package/src/runtime/resolver-runtime-options.ts +50 -0
- package/src/runtime/resolver-shared.ts +113 -0
- package/src/runtime/resolver-span-attributes.ts +139 -0
- package/src/runtime/resolver-telemetry.ts +362 -0
- package/src/runtime/resolver-usage.ts +99 -0
- package/src/runtime/resolver-vendors/bindings.ts +61 -0
- package/src/runtime/resolver-vendors/browser.ts +591 -0
- package/src/runtime/resolver-vendors/capsolver.ts +727 -0
- package/src/runtime/resolver-vendors/hosts.ts +81 -0
- package/src/runtime/resolver-vendors/hypersolutions.ts +480 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +555 -0
- package/src/runtime/resolver-vendors/types.ts +261 -0
- package/src/runtime/resolver.ts +1543 -0
- package/src/runtime/state-telemetry.ts +298 -0
- package/src/runtime/state.ts +435 -89
- package/src/runtime/stealth-akamai-sbsd.ts +207 -0
- package/src/runtime/stealth-cookies.ts +155 -0
- package/src/runtime/stealth-owned-headers.ts +67 -0
- package/src/runtime/stealth-telemetry.ts +309 -0
- package/src/runtime/stealth.ts +2275 -372
- package/src/runtime/stt-telemetry.ts +282 -0
- package/src/runtime/stt.ts +96 -91
- package/src/runtime/timeout.ts +18 -0
- package/src/runtime/trace-config.ts +78 -0
- package/src/runtime/trace.ts +257 -35
- package/src/runtime-boundary-lint.ts +707 -0
- package/src/schema.ts +80 -2
- package/src/serve.ts +6 -1
- package/src/server/error-observability.ts +1 -0
- package/src/server/error-stack-frames.ts +214 -0
- package/src/server/index.ts +184 -2
- package/src/server/runtime-diagnostics.ts +522 -0
- package/src/server/sdk-version.ts +22 -0
- package/src/server/self-test-input-tokens.ts +29 -14
- package/src/server/self-test-redaction.ts +10 -40
- package/src/server/self-test.ts +205 -89
- package/src/server/sensitive-values.ts +211 -0
- package/src/server/serve-implementation.ts +4596 -0
- package/src/server/serve.ts +1 -1685
- package/src/server/state-backend-report.ts +185 -0
- package/src/server/trace-output.ts +189 -0
- package/src/server/types.ts +34 -1
- 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 +310 -0
- package/src/stateful/stateful-provider-runtime-context.ts +96 -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 +212 -231
- package/src/stream-evidence.ts +988 -0
- package/src/stream.ts +8 -1
- package/src/testing/index.ts +10 -1
- package/src/testing/run.ts +685 -19
- package/src/trace-sanitization.ts +110 -0
- package/src/types.ts +1014 -273
- package/src/typescript-module.ts +100 -0
- package/dist/choice-token.d.ts +0 -24
- package/dist/choice-token.js +0 -74
- package/dist/runtime/choice.d.ts +0 -15
- package/dist/runtime/choice.js +0 -413
- package/src/choice-token.ts +0 -165
- package/src/runtime/choice.ts +0 -686
package/dist/runtime/http.js
CHANGED
|
@@ -1,16 +1,77 @@
|
|
|
1
|
-
import { resolveProxyConfigAsync } from "../config/loader.js";
|
|
1
|
+
import { policyRotatesTransportVendorChain, resolvePolicyTransportAttemptCap, resolveProxyConfigAsync, } from "../config/loader.js";
|
|
2
2
|
import { ProviderError, TransportError } from "../errors.js";
|
|
3
3
|
import { parseSseStream, readableBytes, readableLines, readableTextChunks } from "../stream.js";
|
|
4
4
|
import { computeProxyAttemptIndex, computeProxyTransportRetryDelayMs, createDefaultProxyTransportRetryOptions, isProxyTransportRetryMethod, normalizeProxyTransportRetryOptions, proxyTransportRetryErrorCode, proxyTransportRetryErrorStatus, shouldRetryProxyTransportAttempt, validateUnsafeProxyTransportRetryMethods, } from "./proxy-retry-policy.js";
|
|
5
|
-
import {
|
|
5
|
+
import { httpTelemetryErrorCode, } from "./http-telemetry.js";
|
|
6
|
+
import { invalidRedirectPolicy, isRedirectStatus, nextSameOriginRedirectHop, normalizeRedirectPolicy, } from "./redirects.js";
|
|
7
|
+
import { normalizeHttpRequestBody, redactSensitiveError, redactSensitiveRequestError, serializeRequestUrl, } from "./request-options.js";
|
|
8
|
+
import { startHttpTelemetry } from "./http-telemetry-guard.js";
|
|
9
|
+
import { scopedHttpTelemetry } from "./http-telemetry-binding.js";
|
|
6
10
|
const DEFAULT_HTTP_BASE_URL = "http://localhost";
|
|
11
|
+
function httpAttemptFailure(error, status) {
|
|
12
|
+
try {
|
|
13
|
+
return {
|
|
14
|
+
e: httpTelemetryErrorCode(proxyTransportRetryErrorCode(error)),
|
|
15
|
+
status: proxyTransportRetryErrorStatus(error) ?? status,
|
|
16
|
+
...(error instanceof Error
|
|
17
|
+
? {
|
|
18
|
+
diagnostics: {
|
|
19
|
+
name: error.name,
|
|
20
|
+
message: error.message,
|
|
21
|
+
...(error.cause instanceof Error
|
|
22
|
+
? { cause: { name: error.cause.name, message: error.cause.message } }
|
|
23
|
+
: {}),
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
: {}),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return { e: "other" };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
7
33
|
function isHttpStatusOutcome(outcome) {
|
|
8
34
|
return "kind" in outcome && outcome.kind === "http-status";
|
|
9
35
|
}
|
|
10
|
-
|
|
36
|
+
function isDedupeSkipOutcome(outcome) {
|
|
37
|
+
return "kind" in outcome && outcome.kind === "dedupe-skip";
|
|
38
|
+
}
|
|
39
|
+
function toAmbientCancellationError(signal, error = signal.reason) {
|
|
40
|
+
if (error instanceof TransportError && error.code === "transport_cancelled") {
|
|
41
|
+
return error;
|
|
42
|
+
}
|
|
43
|
+
return new TransportError("Request cancelled", {
|
|
44
|
+
code: "transport_cancelled",
|
|
45
|
+
status: 0,
|
|
46
|
+
retryable: false,
|
|
47
|
+
...(error !== undefined
|
|
48
|
+
? { cause: error instanceof Error ? error : new Error(String(error)) }
|
|
49
|
+
: {}),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function throwIfAmbientAborted(signal) {
|
|
53
|
+
if (signal?.aborted)
|
|
54
|
+
throw toAmbientCancellationError(signal);
|
|
55
|
+
}
|
|
56
|
+
async function sleep(ms, signal) {
|
|
57
|
+
throwIfAmbientAborted(signal);
|
|
11
58
|
if (ms <= 0)
|
|
12
59
|
return;
|
|
13
|
-
|
|
60
|
+
if (!signal) {
|
|
61
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
await new Promise((resolve, reject) => {
|
|
65
|
+
const onAbort = () => {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
reject(toAmbientCancellationError(signal));
|
|
68
|
+
};
|
|
69
|
+
const timer = setTimeout(() => {
|
|
70
|
+
signal.removeEventListener("abort", onAbort);
|
|
71
|
+
resolve();
|
|
72
|
+
}, ms);
|
|
73
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
74
|
+
});
|
|
14
75
|
}
|
|
15
76
|
function toUpstreamHttpError(status) {
|
|
16
77
|
return new TransportError(`Upstream request failed with status ${status}`, {
|
|
@@ -23,19 +84,92 @@ function hasHeader(headers, name) {
|
|
|
23
84
|
const needle = name.toLowerCase();
|
|
24
85
|
return Object.keys(headers).some((key) => key.toLowerCase() === needle);
|
|
25
86
|
}
|
|
26
|
-
function
|
|
27
|
-
const
|
|
87
|
+
function withClientHeaderDefaults(headers, clientOptions, body) {
|
|
88
|
+
const merged = {
|
|
28
89
|
...(clientOptions.userAgent ? { "User-Agent": clientOptions.userAgent } : {}),
|
|
29
|
-
...
|
|
90
|
+
...headers,
|
|
30
91
|
};
|
|
31
|
-
if (body !== undefined && !hasHeader(
|
|
32
|
-
|
|
92
|
+
if (body !== undefined && !hasHeader(merged, "Content-Type")) {
|
|
93
|
+
merged["Content-Type"] = "application/json";
|
|
33
94
|
}
|
|
95
|
+
return merged;
|
|
96
|
+
}
|
|
97
|
+
function withClientHeaders(options, clientOptions, body) {
|
|
98
|
+
// The ambient client signal is merged into the per-request one here so every
|
|
99
|
+
// downstream read (retry loop, sleep, body consumption) sees a single signal.
|
|
100
|
+
// The header-factory branch needs it too, otherwise factory-header requests
|
|
101
|
+
// would lose ambient cancellation entirely.
|
|
102
|
+
const signal = mergeAbortSignals(clientOptions.signal, options?.signal);
|
|
103
|
+
// A header factory is resolved per issued attempt (resolveAttemptHeaders);
|
|
104
|
+
// the client defaults are applied to its output at that point.
|
|
105
|
+
if (typeof options?.headers === "function")
|
|
106
|
+
return { ...options, signal };
|
|
34
107
|
return {
|
|
35
108
|
...options,
|
|
36
|
-
headers,
|
|
109
|
+
headers: withClientHeaderDefaults(options?.headers, clientOptions, body),
|
|
110
|
+
signal,
|
|
37
111
|
};
|
|
38
112
|
}
|
|
113
|
+
function headerFactoryFailure(message, cause) {
|
|
114
|
+
// A dedicated code keeps a broken proof minter out of the retry predicate:
|
|
115
|
+
// toHttpTransportError would otherwise brand a plain throw as
|
|
116
|
+
// transport_network_error, which the default retry policy re-issues.
|
|
117
|
+
// retryable:false is explicit so the served error envelope does not
|
|
118
|
+
// advertise a deterministic signing failure as retryable, and
|
|
119
|
+
// category:provider_error keeps a provider-local minting bug out of the
|
|
120
|
+
// upstream-failure signal (status 0 would otherwise fall through the
|
|
121
|
+
// observability ladder to upstream_http / source upstream_failure).
|
|
122
|
+
return new TransportError(message, {
|
|
123
|
+
code: "http_header_factory_failed",
|
|
124
|
+
status: 0,
|
|
125
|
+
retryable: false,
|
|
126
|
+
category: "provider_error",
|
|
127
|
+
...(cause instanceof Error ? { cause } : {}),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function isHeaderRecord(value) {
|
|
131
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
132
|
+
}
|
|
133
|
+
// Settle `promise`, or reject with the signal's reason as soon as it aborts, so
|
|
134
|
+
// a pending header factory cannot outlive the request deadline or an ambient
|
|
135
|
+
// cancellation (native fetch has not started yet, so nothing else observes them).
|
|
136
|
+
function settleBeforeAbort(promise, signal) {
|
|
137
|
+
if (!signal)
|
|
138
|
+
return promise;
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
const onAbort = () => reject(signal.reason);
|
|
141
|
+
if (signal.aborted)
|
|
142
|
+
onAbort();
|
|
143
|
+
else
|
|
144
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
145
|
+
// Always attach handlers: a late rejection after the abort must settle
|
|
146
|
+
// here (a no-op on the already-rejected promise), never go unhandled.
|
|
147
|
+
promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
async function resolveAttemptHeaders(options, clientOptions, attempt, signal) {
|
|
151
|
+
const { headers } = options;
|
|
152
|
+
if (typeof headers !== "function")
|
|
153
|
+
return headers;
|
|
154
|
+
// The per-call timeout may have fired during proxy resolution: do not start
|
|
155
|
+
// the factory at all once the request is already aborted.
|
|
156
|
+
if (signal?.aborted)
|
|
157
|
+
throw signal.reason;
|
|
158
|
+
let minted;
|
|
159
|
+
try {
|
|
160
|
+
minted = await settleBeforeAbort(Promise.resolve().then(() => headers(attempt())), signal);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
// An abort is classified by toHttpTransportError (cancelled / timed out).
|
|
164
|
+
if (signal?.aborted)
|
|
165
|
+
throw error;
|
|
166
|
+
throw headerFactoryFailure("Request header factory failed", error);
|
|
167
|
+
}
|
|
168
|
+
if (!isHeaderRecord(minted)) {
|
|
169
|
+
throw headerFactoryFailure("Request header factory must return a header record");
|
|
170
|
+
}
|
|
171
|
+
return withClientHeaderDefaults(minted, clientOptions, options.body);
|
|
172
|
+
}
|
|
39
173
|
function parseHttpData(body, headers) {
|
|
40
174
|
const contentType = headers["content-type"] ?? headers["Content-Type"] ?? headers["CONTENT-TYPE"];
|
|
41
175
|
if (contentType?.includes("application/json")) {
|
|
@@ -49,7 +183,17 @@ function parseJson(body) {
|
|
|
49
183
|
function isTimeoutMessage(message) {
|
|
50
184
|
return /\b(timed out|timeout|deadline exceeded)\b/i.test(message);
|
|
51
185
|
}
|
|
52
|
-
function toHttpTransportError(error) {
|
|
186
|
+
function toHttpTransportError(error, ambientSignal, timeoutSignal) {
|
|
187
|
+
if (ambientSignal?.aborted) {
|
|
188
|
+
return toAmbientCancellationError(ambientSignal, error);
|
|
189
|
+
}
|
|
190
|
+
if (timeoutSignal?.aborted) {
|
|
191
|
+
return new TransportError("Request timed out", {
|
|
192
|
+
code: "transport_timeout",
|
|
193
|
+
status: 0,
|
|
194
|
+
...(error instanceof Error ? { cause: error } : {}),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
53
197
|
if (error instanceof TransportError) {
|
|
54
198
|
if (error.code) {
|
|
55
199
|
return error;
|
|
@@ -121,9 +265,108 @@ function requireNativeResponseBody(response) {
|
|
|
121
265
|
}
|
|
122
266
|
return response.body;
|
|
123
267
|
}
|
|
124
|
-
function
|
|
268
|
+
function mergeAbortSignals(...signals) {
|
|
269
|
+
const activeSignals = signals.filter((signal) => signal != null);
|
|
270
|
+
if (activeSignals.length === 0)
|
|
271
|
+
return undefined;
|
|
272
|
+
if (activeSignals.length === 1)
|
|
273
|
+
return activeSignals[0];
|
|
274
|
+
return AbortSignal.any(activeSignals);
|
|
275
|
+
}
|
|
276
|
+
function cancelStreamOnAbort(body, signal) {
|
|
277
|
+
if (!signal)
|
|
278
|
+
return body;
|
|
279
|
+
let reader;
|
|
280
|
+
let finished = false;
|
|
281
|
+
let streamController;
|
|
282
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
283
|
+
const onAbort = () => {
|
|
284
|
+
if (finished)
|
|
285
|
+
return;
|
|
286
|
+
finished = true;
|
|
287
|
+
cleanup();
|
|
288
|
+
const reason = toAmbientCancellationError(signal);
|
|
289
|
+
streamController?.error(reason);
|
|
290
|
+
const cancellation = reader ? reader.cancel(reason) : body.cancel(reason);
|
|
291
|
+
void cancellation.catch(() => undefined).finally(() => reader?.releaseLock());
|
|
292
|
+
};
|
|
293
|
+
return new ReadableStream({
|
|
294
|
+
start(controller) {
|
|
295
|
+
streamController = controller;
|
|
296
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
297
|
+
if (signal.aborted)
|
|
298
|
+
onAbort();
|
|
299
|
+
},
|
|
300
|
+
async pull(controller) {
|
|
301
|
+
try {
|
|
302
|
+
reader ??= body.getReader();
|
|
303
|
+
const chunk = await reader.read();
|
|
304
|
+
if (finished)
|
|
305
|
+
return;
|
|
306
|
+
if (chunk.done) {
|
|
307
|
+
finished = true;
|
|
308
|
+
cleanup();
|
|
309
|
+
controller.close();
|
|
310
|
+
reader.releaseLock();
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
controller.enqueue(chunk.value);
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
if (finished)
|
|
317
|
+
return;
|
|
318
|
+
finished = true;
|
|
319
|
+
cleanup();
|
|
320
|
+
controller.error(error);
|
|
321
|
+
reader?.releaseLock();
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
async cancel(reason) {
|
|
325
|
+
if (finished)
|
|
326
|
+
return;
|
|
327
|
+
finished = true;
|
|
328
|
+
cleanup();
|
|
329
|
+
try {
|
|
330
|
+
await (reader ? reader.cancel(reason) : body.cancel(reason));
|
|
331
|
+
}
|
|
332
|
+
finally {
|
|
333
|
+
reader?.releaseLock();
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
}, { highWaterMark: 0 });
|
|
337
|
+
}
|
|
338
|
+
function sanitizeStreamErrors(body, serializedUrl) {
|
|
339
|
+
if (serializedUrl.sensitiveValues.length === 0)
|
|
340
|
+
return body;
|
|
341
|
+
let reader;
|
|
342
|
+
return new ReadableStream({
|
|
343
|
+
async pull(controller) {
|
|
344
|
+
try {
|
|
345
|
+
reader ??= body.getReader();
|
|
346
|
+
const chunk = await reader.read();
|
|
347
|
+
if (chunk.done) {
|
|
348
|
+
controller.close();
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
controller.enqueue(chunk.value);
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
controller.error(redactSensitiveError(error, serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl));
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
async cancel(reason) {
|
|
358
|
+
try {
|
|
359
|
+
await (reader ? reader.cancel(reason) : body.cancel(reason));
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
throw redactSensitiveError(error, serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl);
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
}, { highWaterMark: 0 });
|
|
366
|
+
}
|
|
367
|
+
function toNativeHttpStreamResponse(response, serializedUrl, signal) {
|
|
125
368
|
const headers = Object.fromEntries(response.headers.entries());
|
|
126
|
-
const body = requireNativeResponseBody(response);
|
|
369
|
+
const body = sanitizeStreamErrors(cancelStreamOnAbort(requireNativeResponseBody(response), signal), serializedUrl);
|
|
127
370
|
return {
|
|
128
371
|
body,
|
|
129
372
|
headers,
|
|
@@ -158,24 +401,116 @@ function normalizeHttpMethod(method) {
|
|
|
158
401
|
});
|
|
159
402
|
}
|
|
160
403
|
}
|
|
161
|
-
|
|
404
|
+
/** Whether `url` carries its own scheme, so it needs no `upstream.baseUrl` to resolve. */
|
|
405
|
+
export function isAbsoluteUrl(url) {
|
|
162
406
|
return /^[a-z][a-z\d+\-.]*:/i.test(url);
|
|
163
407
|
}
|
|
164
408
|
function resolveHttpUrl(baseUrl, url) {
|
|
165
409
|
return new URL(url, baseUrl ?? DEFAULT_HTTP_BASE_URL).toString();
|
|
166
410
|
}
|
|
411
|
+
const MAX_HTTP_REDIRECT_HOPS = 20;
|
|
412
|
+
const HTTP_REDIRECT_POLICY_TRANSPORT = {
|
|
413
|
+
label: "ctx.http",
|
|
414
|
+
maxHops: MAX_HTTP_REDIRECT_HOPS,
|
|
415
|
+
};
|
|
416
|
+
const REDIRECT_BODY_HEADERS = new Set([
|
|
417
|
+
"content-encoding",
|
|
418
|
+
"content-language",
|
|
419
|
+
"content-location",
|
|
420
|
+
"content-type",
|
|
421
|
+
]);
|
|
422
|
+
function snapshotHttpRedirectPolicy(options) {
|
|
423
|
+
try {
|
|
424
|
+
return normalizeRedirectPolicy(options.redirectPolicy, HTTP_REDIRECT_POLICY_TRANSPORT);
|
|
425
|
+
}
|
|
426
|
+
catch (error) {
|
|
427
|
+
if (error instanceof TransportError)
|
|
428
|
+
throw error;
|
|
429
|
+
throw invalidRedirectPolicy(HTTP_REDIRECT_POLICY_TRANSPORT, "could not be read safely", error instanceof Error ? error : undefined);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
function withoutRedirectBodyHeaders(headers) {
|
|
433
|
+
const nextHeaders = new Headers(headers);
|
|
434
|
+
for (const name of REDIRECT_BODY_HEADERS)
|
|
435
|
+
nextHeaders.delete(name);
|
|
436
|
+
return nextHeaders;
|
|
437
|
+
}
|
|
438
|
+
function discardRedirectResponseBody(response) {
|
|
439
|
+
try {
|
|
440
|
+
const cancellation = response.body?.cancel();
|
|
441
|
+
if (cancellation)
|
|
442
|
+
void cancellation.catch(() => undefined);
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
// The redirect decision is security-significant and must not be replaced
|
|
446
|
+
// or delayed by an upstream body's cancellation failure. Cancellation was
|
|
447
|
+
// attempted; redirect evaluation continues without awaiting its completion.
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
async function fetchWithHttpRedirectPolicy(requestUrl, requestInit, policy) {
|
|
451
|
+
if (!policy)
|
|
452
|
+
return fetch(requestUrl, requestInit);
|
|
453
|
+
const initialUrl = new URL(requestUrl);
|
|
454
|
+
if (initialUrl.protocol !== "http:" && initialUrl.protocol !== "https:") {
|
|
455
|
+
throw new TransportError("ctx.http redirectPolicy requires an HTTP(S) origin", {
|
|
456
|
+
code: "transport_invalid_url",
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
const initialOrigin = initialUrl.origin;
|
|
460
|
+
let currentUrl = requestUrl;
|
|
461
|
+
let method = normalizeHttpMethod(requestInit.method ?? "GET");
|
|
462
|
+
let body = requestInit.body;
|
|
463
|
+
let headers = requestInit.headers;
|
|
464
|
+
let followedHops = 0;
|
|
465
|
+
const visitedRequests = new Set([`${method} ${currentUrl}`]);
|
|
466
|
+
while (true) {
|
|
467
|
+
const response = await fetch(currentUrl, {
|
|
468
|
+
...requestInit,
|
|
469
|
+
body,
|
|
470
|
+
headers,
|
|
471
|
+
method,
|
|
472
|
+
redirect: "manual",
|
|
473
|
+
});
|
|
474
|
+
if (!isRedirectStatus(response.status))
|
|
475
|
+
return response;
|
|
476
|
+
const location = response.headers.get("location");
|
|
477
|
+
discardRedirectResponseBody(response);
|
|
478
|
+
const decision = nextSameOriginRedirectHop({
|
|
479
|
+
policy,
|
|
480
|
+
initialOrigin,
|
|
481
|
+
currentUrl,
|
|
482
|
+
responseUrl: currentUrl,
|
|
483
|
+
status: response.status,
|
|
484
|
+
method,
|
|
485
|
+
location: location ?? undefined,
|
|
486
|
+
followedHops,
|
|
487
|
+
visitedRequests,
|
|
488
|
+
});
|
|
489
|
+
if (decision.nextMethod !== method) {
|
|
490
|
+
body = undefined;
|
|
491
|
+
headers = withoutRedirectBodyHeaders(headers);
|
|
492
|
+
}
|
|
493
|
+
method = decision.nextMethod;
|
|
494
|
+
currentUrl = decision.nextUrl;
|
|
495
|
+
followedHops += 1;
|
|
496
|
+
visitedRequests.add(`${method} ${currentUrl}`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
167
499
|
async function resolveNativeProxy(options, clientOptions, warn, proxyAttemptOffset = 0) {
|
|
168
500
|
const resolvedProxy = await resolveProxyConfigAsync({
|
|
169
501
|
proxy: options.proxy ?? clientOptions.proxy,
|
|
170
502
|
upstream: clientOptions.upstream,
|
|
171
|
-
apifuseConfig: clientOptions.apifuseConfig,
|
|
172
503
|
proxyPolicy: clientOptions.proxyPolicy,
|
|
173
504
|
affinityKey: clientOptions.affinityKey,
|
|
174
505
|
proxyAttempt: computeProxyAttemptIndex({
|
|
175
506
|
baseProxyAttempt: clientOptions.proxyAttempt,
|
|
176
507
|
retryAttemptOffset: proxyAttemptOffset,
|
|
177
508
|
}),
|
|
509
|
+
// Bun's native fetch proxy option tunnels HTTP CONNECT only; SOCKS5 is not
|
|
510
|
+
// supported here, so a socks5 policy fails loudly rather than downgrading.
|
|
511
|
+
transportProtocols: ["http"],
|
|
178
512
|
telemetry: clientOptions.telemetry,
|
|
513
|
+
engineCredentials: clientOptions.engineCredentials,
|
|
179
514
|
});
|
|
180
515
|
if (resolvedProxy.shouldWarn) {
|
|
181
516
|
warn("[provider-sdk] Provider requested proxy routing, but no proxy URL was configured. Continuing without proxy.");
|
|
@@ -196,27 +531,62 @@ function normalizeNativeFetchBody(body) {
|
|
|
196
531
|
copied.set(normalized);
|
|
197
532
|
return copied.buffer;
|
|
198
533
|
}
|
|
199
|
-
|
|
200
|
-
|
|
534
|
+
function serializeHttpRequestUrl(baseUrl, url, options) {
|
|
535
|
+
try {
|
|
536
|
+
return serializeRequestUrl(resolveHttpUrl(baseUrl, url), options.params, options.sensitiveParams);
|
|
537
|
+
}
|
|
538
|
+
catch (error) {
|
|
539
|
+
throw redactSensitiveRequestError(error, url, options.sensitiveParams);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, warn, statusRetryCodes, proxyAttemptOffset = 0, dedupe, observeProxy, nextHeaderAttempt = () => 1) {
|
|
543
|
+
const serializedUrl = serializeHttpRequestUrl(baseUrl, url, options);
|
|
544
|
+
const { requestUrl } = serializedUrl;
|
|
201
545
|
const controller = options.timeout ? new AbortController() : undefined;
|
|
202
|
-
const
|
|
546
|
+
const signal = mergeAbortSignals(options.signal, controller?.signal);
|
|
547
|
+
let timeoutHandle = options.timeout
|
|
203
548
|
? setTimeout(() => controller?.abort(), options.timeout)
|
|
204
549
|
: undefined;
|
|
550
|
+
const disarmTimeout = () => {
|
|
551
|
+
if (!timeoutHandle)
|
|
552
|
+
return;
|
|
553
|
+
clearTimeout(timeoutHandle);
|
|
554
|
+
timeoutHandle = undefined;
|
|
555
|
+
};
|
|
205
556
|
let proxy;
|
|
206
557
|
try {
|
|
558
|
+
throwIfAmbientAborted(options.signal);
|
|
559
|
+
// Resolve inside the try (and after the timeout is armed) so allocator
|
|
560
|
+
// failures are branded as TransportErrors and count against the request
|
|
561
|
+
// deadline, exactly as an inline resolve would.
|
|
207
562
|
proxy = await resolveNativeProxy(options, clientOptions, warn, proxyAttemptOffset);
|
|
563
|
+
observeProxy?.(Boolean(proxy));
|
|
564
|
+
throwIfAmbientAborted(options.signal);
|
|
565
|
+
// For a registry allocator chain, skip an endpoint a prior attempt already
|
|
566
|
+
// tried rather than re-issuing the same request. Returning the sentinel
|
|
567
|
+
// (instead of breaking) lets the loop keep advancing the flat offset until
|
|
568
|
+
// it crosses into the fallback vendor's pool span.
|
|
569
|
+
if (dedupe && proxy) {
|
|
570
|
+
if (dedupe.attempted.has(proxy)) {
|
|
571
|
+
return { kind: "dedupe-skip" };
|
|
572
|
+
}
|
|
573
|
+
dedupe.attempted.add(proxy);
|
|
574
|
+
}
|
|
575
|
+
// Resolve after the dedupe check so a skipped duplicate offset never burns
|
|
576
|
+
// a single-use proof, and with the serialized URL so URL-bound proofs
|
|
577
|
+
// match what is actually issued.
|
|
578
|
+
const requestHeaders = await resolveAttemptHeaders(options, clientOptions, () => ({ attempt: nextHeaderAttempt(), url: requestUrl, method }), signal);
|
|
208
579
|
const requestInit = {
|
|
209
|
-
headers:
|
|
580
|
+
headers: requestHeaders,
|
|
210
581
|
method,
|
|
211
582
|
...(proxy ? { proxy } : {}),
|
|
212
|
-
signal:
|
|
583
|
+
...(signal ? { signal } : {}),
|
|
213
584
|
};
|
|
214
585
|
if (options.body !== undefined) {
|
|
215
586
|
requestInit.body = normalizeNativeFetchBody(options.body);
|
|
216
587
|
}
|
|
217
|
-
const response = await
|
|
218
|
-
|
|
219
|
-
});
|
|
588
|
+
const response = await fetchWithHttpRedirectPolicy(requestUrl, requestInit, options.redirectPolicy);
|
|
589
|
+
observeProxy?.(Boolean(proxy), response.status);
|
|
220
590
|
const headers = Object.fromEntries(response.headers.entries());
|
|
221
591
|
if (statusRetryCodes && response.status >= 400) {
|
|
222
592
|
await drainNativeResponseBody(response);
|
|
@@ -235,41 +605,53 @@ async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, war
|
|
|
235
605
|
status: response.status,
|
|
236
606
|
});
|
|
237
607
|
}
|
|
238
|
-
|
|
608
|
+
// `timeout` bounds the header phase (types.ts RequestOptions.timeout), so it
|
|
609
|
+
// is disarmed before the body is read: awaiting the buffered body inside
|
|
610
|
+
// this try brands a mid-body failure as a transport error, but must not
|
|
611
|
+
// extend the deadline over the download. Cancellation still applies —
|
|
612
|
+
// `signal` stays attached to the native response. The discarded non-2xx
|
|
613
|
+
// bodies drained above stay under the deadline on purpose: nothing else
|
|
614
|
+
// bounds them, and their content is never returned to the caller.
|
|
615
|
+
disarmTimeout();
|
|
616
|
+
return await toNativeHttpResponse(response);
|
|
239
617
|
}
|
|
240
618
|
catch (error) {
|
|
241
619
|
if (error instanceof SyntaxError) {
|
|
242
|
-
throw error;
|
|
620
|
+
throw redactSensitiveError(error, serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl);
|
|
243
621
|
}
|
|
244
|
-
const transportError = toHttpTransportError(error);
|
|
622
|
+
const transportError = redactSensitiveError(toHttpTransportError(error, options.signal, controller?.signal), serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl);
|
|
245
623
|
transportError.proxyUsed = Boolean(proxy);
|
|
246
624
|
throw transportError;
|
|
247
625
|
}
|
|
248
626
|
finally {
|
|
249
|
-
|
|
250
|
-
clearTimeout(timeoutHandle);
|
|
627
|
+
disarmTimeout();
|
|
251
628
|
}
|
|
252
629
|
}
|
|
253
|
-
async function fetchNativeHttpStream(baseUrl, url, method, options, clientOptions, warn) {
|
|
254
|
-
const
|
|
630
|
+
async function fetchNativeHttpStream(baseUrl, url, method, options, clientOptions, warn, observeProxy) {
|
|
631
|
+
const serializedUrl = serializeHttpRequestUrl(baseUrl, url, options);
|
|
632
|
+
const { requestUrl } = serializedUrl;
|
|
255
633
|
const controller = options.timeout ? new AbortController() : undefined;
|
|
634
|
+
const signal = mergeAbortSignals(options.signal, controller?.signal);
|
|
256
635
|
const timeoutHandle = options.timeout
|
|
257
636
|
? setTimeout(() => controller?.abort(), options.timeout)
|
|
258
637
|
: undefined;
|
|
259
638
|
try {
|
|
639
|
+
throwIfAmbientAborted(options.signal);
|
|
260
640
|
const proxy = await resolveNativeProxy(options, clientOptions, warn);
|
|
641
|
+
observeProxy?.(Boolean(proxy));
|
|
642
|
+
throwIfAmbientAborted(options.signal);
|
|
643
|
+
// Streams never retry: a header factory is resolved once, as attempt 1.
|
|
644
|
+
const headers = await resolveAttemptHeaders(options, clientOptions, () => ({ attempt: 1, url: requestUrl, method }), signal);
|
|
261
645
|
const requestInit = {
|
|
262
|
-
headers
|
|
646
|
+
headers,
|
|
263
647
|
method,
|
|
264
648
|
...(proxy ? { proxy } : {}),
|
|
265
|
-
signal:
|
|
649
|
+
...(signal ? { signal } : {}),
|
|
266
650
|
};
|
|
267
651
|
if (options.body !== undefined) {
|
|
268
652
|
requestInit.body = normalizeNativeFetchBody(options.body);
|
|
269
653
|
}
|
|
270
|
-
const response = await
|
|
271
|
-
...requestInit,
|
|
272
|
-
});
|
|
654
|
+
const response = await fetchWithHttpRedirectPolicy(requestUrl, requestInit, options.redirectPolicy);
|
|
273
655
|
if (response.status >= 400 && options.throwOnHttpError !== false) {
|
|
274
656
|
await drainNativeResponseBody(response);
|
|
275
657
|
throw new TransportError(`Upstream request failed with status ${response.status}`, {
|
|
@@ -277,13 +659,15 @@ async function fetchNativeHttpStream(baseUrl, url, method, options, clientOption
|
|
|
277
659
|
status: response.status,
|
|
278
660
|
});
|
|
279
661
|
}
|
|
280
|
-
|
|
662
|
+
// The stream timeout remains header-scoped; composed ambient and local
|
|
663
|
+
// cancellation stays attached to native fetch for the body's full lifetime.
|
|
664
|
+
return toNativeHttpStreamResponse(response, serializedUrl, options.signal);
|
|
281
665
|
}
|
|
282
666
|
catch (error) {
|
|
283
667
|
if (error instanceof SyntaxError) {
|
|
284
|
-
throw error;
|
|
668
|
+
throw redactSensitiveError(error, serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl);
|
|
285
669
|
}
|
|
286
|
-
throw toHttpTransportError(error);
|
|
670
|
+
throw redactSensitiveError(toHttpTransportError(error, options.signal, controller?.signal), serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl);
|
|
287
671
|
}
|
|
288
672
|
finally {
|
|
289
673
|
if (timeoutHandle)
|
|
@@ -301,98 +685,268 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
|
|
|
301
685
|
warn(message);
|
|
302
686
|
};
|
|
303
687
|
async function request(url, method, options = {}) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
headersOptions.throwOnHttpError !== false);
|
|
325
|
-
const attemptOptions = statusRetryEnabled
|
|
326
|
-
? { ...headersOptions, throwOnHttpError: false }
|
|
327
|
-
: headersOptions;
|
|
328
|
-
const executeOnce = (proxyAttemptOffset = 0) => fetchNativeHttp(baseUrl, url, methodName, attemptOptions, clientOptions, warnOnce, statusRetryEnabled ? retryOptions?.statusCodes : undefined, proxyAttemptOffset);
|
|
329
|
-
if (!retryEnabled || !retryOptions) {
|
|
330
|
-
const outcome = await executeOnce();
|
|
331
|
-
if (isHttpStatusOutcome(outcome)) {
|
|
332
|
-
throw toUpstreamHttpError(outcome.status);
|
|
688
|
+
const { explicitRetry, headersOptions, methodName, retryOptions } = (() => {
|
|
689
|
+
try {
|
|
690
|
+
if (!baseUrl && !isAbsoluteUrl(url)) {
|
|
691
|
+
throw new TransportError("ctx.http requires an absolute URL when provider.upstream.baseUrl is not declared", { code: "transport_invalid_url" });
|
|
692
|
+
}
|
|
693
|
+
assertNoHttpTransportOverrides(options);
|
|
694
|
+
const redirectPolicy = snapshotHttpRedirectPolicy(options);
|
|
695
|
+
const headersOptions = {
|
|
696
|
+
...withClientHeaders(options, clientOptions, options.body),
|
|
697
|
+
redirectPolicy,
|
|
698
|
+
};
|
|
699
|
+
const methodName = normalizeHttpMethod(method);
|
|
700
|
+
const explicitRetry = headersOptions.retry !== undefined;
|
|
701
|
+
const retryOptions = normalizeProxyTransportRetryOptions(headersOptions.retry, {
|
|
702
|
+
label: "HTTP",
|
|
703
|
+
}) ??
|
|
704
|
+
(explicitRetry ? undefined : createDefaultProxyTransportRetryOptions({ label: "HTTP" }));
|
|
705
|
+
if (retryOptions)
|
|
706
|
+
validateUnsafeProxyTransportRetryMethods(retryOptions, "HTTP");
|
|
707
|
+
return { explicitRetry, headersOptions, methodName, retryOptions };
|
|
333
708
|
}
|
|
334
|
-
|
|
709
|
+
catch (error) {
|
|
710
|
+
throw redactSensitiveRequestError(error, url, options.sensitiveParams);
|
|
711
|
+
}
|
|
712
|
+
})();
|
|
713
|
+
const requestStarted = performance.now();
|
|
714
|
+
const telemetry = startHttpTelemetry(scopedHttpTelemetry(clientOptions.httpTelemetry), retryOptions?.preset);
|
|
715
|
+
try {
|
|
716
|
+
return await runRequest();
|
|
335
717
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
718
|
+
finally {
|
|
719
|
+
telemetry.finish(performance.now() - requestStarted);
|
|
720
|
+
}
|
|
721
|
+
async function runRequest() {
|
|
722
|
+
const retryEnabled = Boolean(retryOptions &&
|
|
723
|
+
retryOptions.attempts > 1 &&
|
|
724
|
+
isProxyTransportRetryMethod(methodName, retryOptions));
|
|
725
|
+
const statusRetryEnabled = Boolean(retryEnabled &&
|
|
726
|
+
explicitRetry &&
|
|
727
|
+
retryOptions &&
|
|
728
|
+
retryOptions.statusCodes.length > 0 &&
|
|
729
|
+
headersOptions.throwOnHttpError !== false);
|
|
730
|
+
const attemptOptions = statusRetryEnabled
|
|
731
|
+
? { ...headersOptions, throwOnHttpError: false }
|
|
732
|
+
: headersOptions;
|
|
733
|
+
// Span the whole vendor chain on transport failures. Like ctx.stealth, a
|
|
734
|
+
// policy-managed proxy resolves a *different* endpoint/vendor per attempt
|
|
735
|
+
// (the flat proxyAttemptOffset rotates across the concatenated vendor pool
|
|
736
|
+
// spans), so a transport failure should advance to the next endpoint —
|
|
737
|
+
// potentially crossing into the fallback vendor — rather than stopping at
|
|
738
|
+
// the per-endpoint retry budget and stranding the request on the primary
|
|
739
|
+
// vendor. resolvePolicyTransportAttemptCap widens the cap to the chain span
|
|
740
|
+
// only for implicit, safe-method allocator requests; explicit retry
|
|
741
|
+
// policies (their documented `attempts` ceiling), unsafe methods, and
|
|
742
|
+
// static/non-registry vendors keep the retry budget. Status-code retries
|
|
743
|
+
// stay bounded by the retry budget regardless; only transport rotation gets
|
|
744
|
+
// the full span.
|
|
745
|
+
const policyProxy = (() => {
|
|
746
|
+
const policy = clientOptions.proxyPolicy ?? clientOptions.upstream?.proxy;
|
|
747
|
+
return policy && typeof policy === "object" ? policy : undefined;
|
|
748
|
+
})();
|
|
749
|
+
const usesPolicyAllocator = Boolean(policyProxy) && !options.proxy && !clientOptions.proxy;
|
|
750
|
+
const transportAttemptCap = retryOptions
|
|
751
|
+
? resolvePolicyTransportAttemptCap({
|
|
752
|
+
policy: policyProxy,
|
|
753
|
+
usesPolicyAllocator,
|
|
754
|
+
retryAttempts: retryOptions.attempts,
|
|
755
|
+
explicitRetry,
|
|
756
|
+
method: methodName,
|
|
757
|
+
})
|
|
758
|
+
: 1;
|
|
759
|
+
// Track resolved endpoints across a policy-allocator chain. Successive
|
|
760
|
+
// attempts rotate the flat offset across the concatenated vendor pool
|
|
761
|
+
// spans, but an under-filled allocation (fewer live endpoints than the
|
|
762
|
+
// configured pool size) makes the modulo mapping repeat endpoints before
|
|
763
|
+
// the offset reaches the next vendor. Rather than re-hammering an
|
|
764
|
+
// already-tried endpoint under backoff, fetchNativeHttp returns a skip
|
|
765
|
+
// sentinel for a duplicate; the loop then advances the flat offset without
|
|
766
|
+
// issuing the request, so it keeps walking toward — and into — the fallback
|
|
767
|
+
// vendor's pool span instead of stalling on the primary vendor.
|
|
768
|
+
// De-duplication is gated on the SAME predicate that widens the attempt cap
|
|
769
|
+
// (implicit, safe-method, registry-chain rotation). It must NOT engage for
|
|
770
|
+
// an explicit retry policy: there the caller's `attempts` count is the
|
|
771
|
+
// contract and each attempt must issue against whatever endpoint it resolves
|
|
772
|
+
// — even a repeat — instead of being silently skipped (which would collapse
|
|
773
|
+
// a `poolSize: 1` + `attempts: 3` request to a single fetch).
|
|
774
|
+
const dedupeAllocatorEndpoints = policyRotatesTransportVendorChain({
|
|
775
|
+
policy: policyProxy,
|
|
776
|
+
usesPolicyAllocator,
|
|
777
|
+
explicitRetry,
|
|
778
|
+
method: methodName,
|
|
779
|
+
});
|
|
780
|
+
const dedupeContext = dedupeAllocatorEndpoints ? { attempted: new Set() } : undefined;
|
|
781
|
+
// Numbers the attempts a header factory sees: only requests that reached
|
|
782
|
+
// header resolution count, so a failed proxy allocation (which increments
|
|
783
|
+
// the telemetry `issued` count below) or a dedupe-skipped offset does not
|
|
784
|
+
// make the first request that is actually built report `attempt: 2`.
|
|
785
|
+
let headerAttempts = 0;
|
|
786
|
+
const nextHeaderAttempt = () => {
|
|
787
|
+
headerAttempts += 1;
|
|
788
|
+
return headerAttempts;
|
|
789
|
+
};
|
|
790
|
+
const executeOnce = async (proxyAttemptOffset = 0) => {
|
|
791
|
+
const started = performance.now();
|
|
792
|
+
let proxyUsed = false;
|
|
793
|
+
let observedStatus;
|
|
794
|
+
const record = (event) => {
|
|
795
|
+
telemetry.recordAttempt({ ...event, ms: performance.now() - started, proxyUsed });
|
|
796
|
+
};
|
|
797
|
+
try {
|
|
798
|
+
const outcome = await fetchNativeHttp(baseUrl, url, methodName, attemptOptions, clientOptions, warnOnce, statusRetryEnabled ? retryOptions?.statusCodes : undefined, proxyAttemptOffset, dedupeContext, (used, status) => {
|
|
799
|
+
proxyUsed = used;
|
|
800
|
+
observedStatus = status;
|
|
801
|
+
}, nextHeaderAttempt);
|
|
802
|
+
if (!isDedupeSkipOutcome(outcome))
|
|
803
|
+
record({
|
|
804
|
+
status: outcome.status,
|
|
805
|
+
...(isHttpStatusOutcome(outcome)
|
|
806
|
+
? { e: "upstream_http_error", statusRetry: true }
|
|
807
|
+
: {}),
|
|
808
|
+
});
|
|
809
|
+
return outcome;
|
|
810
|
+
}
|
|
811
|
+
catch (error) {
|
|
812
|
+
// Record before the retry loop can catch/rethrow a terminal status.
|
|
813
|
+
record(httpAttemptFailure(error, observedStatus));
|
|
814
|
+
throw error;
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
if (!retryEnabled || !retryOptions) {
|
|
818
|
+
throwIfAmbientAborted(headersOptions.signal);
|
|
819
|
+
const outcome = await executeOnce();
|
|
820
|
+
if (isDedupeSkipOutcome(outcome)) {
|
|
821
|
+
// Single-shot path never de-duplicates (dedupeContext is undefined),
|
|
822
|
+
// but keep the union total.
|
|
823
|
+
throw new TransportError("HTTP request produced no terminal result", {
|
|
824
|
+
code: "retry_exhausted",
|
|
825
|
+
});
|
|
826
|
+
}
|
|
341
827
|
if (isHttpStatusOutcome(outcome)) {
|
|
342
|
-
lastStatus = outcome.status;
|
|
343
|
-
if (outcome.retryable && attempt < retryOptions.attempts) {
|
|
344
|
-
await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt, outcome.headers));
|
|
345
|
-
continue;
|
|
346
|
-
}
|
|
347
828
|
throw toUpstreamHttpError(outcome.status);
|
|
348
829
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
830
|
+
return outcome;
|
|
831
|
+
}
|
|
832
|
+
let lastError;
|
|
833
|
+
// `attempt` walks the flat proxy offset across the full chain span; `issued`
|
|
834
|
+
// counts requests that were actually sent (skipped duplicate offsets do not
|
|
835
|
+
// increment it). Retry summaries and the status-retry budget must reflect
|
|
836
|
+
// issued requests, not the raw offset, so they stay accurate when partial
|
|
837
|
+
// allocations skip offsets.
|
|
838
|
+
let issued = 0;
|
|
839
|
+
for (let attempt = 1; attempt <= transportAttemptCap; attempt += 1) {
|
|
840
|
+
throwIfAmbientAborted(headersOptions.signal);
|
|
841
|
+
// Whether this offset actually issued a request (vs. a skipped duplicate),
|
|
842
|
+
// so the catch counts a thrown *transport* failure once without
|
|
843
|
+
// double-counting a status outcome that already incremented before it
|
|
844
|
+
// re-threw as an upstream HTTP error.
|
|
845
|
+
let issuedThisAttempt = false;
|
|
846
|
+
try {
|
|
847
|
+
const outcome = await executeOnce(attempt - 1);
|
|
848
|
+
if (isDedupeSkipOutcome(outcome)) {
|
|
849
|
+
// Duplicate endpoint from a partial allocation: advance the flat
|
|
850
|
+
// offset without issuing the request (no backoff, not a failure) so
|
|
851
|
+
// the loop keeps rotating toward the fallback vendor.
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
854
|
+
issued += 1;
|
|
855
|
+
issuedThisAttempt = true;
|
|
856
|
+
if (isHttpStatusOutcome(outcome)) {
|
|
857
|
+
if (outcome.retryable && issued < retryOptions.attempts) {
|
|
858
|
+
await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt, outcome.headers), headersOptions.signal);
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
throw toUpstreamHttpError(outcome.status);
|
|
862
|
+
}
|
|
863
|
+
const response = outcome;
|
|
864
|
+
if (response.status >= 400 && headersOptions.throwOnHttpError !== false) {
|
|
865
|
+
throw toUpstreamHttpError(response.status);
|
|
866
|
+
}
|
|
867
|
+
if (issued > 1) {
|
|
868
|
+
const summary = telemetry.toTenantRetryPayload();
|
|
869
|
+
if (summary)
|
|
870
|
+
telemetry.observe(() => clientOptions.onRetrySummary?.(summary));
|
|
871
|
+
}
|
|
872
|
+
return response;
|
|
352
873
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
874
|
+
catch (error) {
|
|
875
|
+
throwIfAmbientAborted(headersOptions.signal);
|
|
876
|
+
if (!issuedThisAttempt)
|
|
877
|
+
issued += 1;
|
|
878
|
+
lastError = error;
|
|
879
|
+
const proxyUsed = Boolean(error.proxyUsed);
|
|
880
|
+
if (attempt < transportAttemptCap &&
|
|
881
|
+
shouldRetryProxyTransportAttempt({
|
|
882
|
+
error,
|
|
883
|
+
explicitRetry,
|
|
884
|
+
method: methodName,
|
|
885
|
+
options: retryOptions,
|
|
886
|
+
proxyUsed,
|
|
887
|
+
})) {
|
|
888
|
+
await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt), headersOptions.signal);
|
|
889
|
+
continue;
|
|
890
|
+
}
|
|
891
|
+
throw error;
|
|
363
892
|
}
|
|
364
|
-
return response;
|
|
365
893
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
error,
|
|
373
|
-
explicitRetry,
|
|
374
|
-
method: methodName,
|
|
375
|
-
options: retryOptions,
|
|
376
|
-
proxyUsed,
|
|
377
|
-
})) {
|
|
378
|
-
await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt));
|
|
379
|
-
continue;
|
|
380
|
-
}
|
|
381
|
-
throw error;
|
|
894
|
+
// Reached when the attempt cap is consumed without a terminal outcome —
|
|
895
|
+
// e.g. the final offsets of a partial allocation all resolved to
|
|
896
|
+
// already-tried endpoints and were skipped. Surface the last real transport
|
|
897
|
+
// failure rather than a synthetic exhaustion error.
|
|
898
|
+
if (lastError !== undefined) {
|
|
899
|
+
throw lastError;
|
|
382
900
|
}
|
|
901
|
+
throw new TransportError("HTTP retry exhausted without a terminal result", {
|
|
902
|
+
code: "retry_exhausted",
|
|
903
|
+
});
|
|
383
904
|
}
|
|
384
|
-
throw new TransportError("HTTP retry exhausted without a terminal result", {
|
|
385
|
-
code: "retry_exhausted",
|
|
386
|
-
});
|
|
387
905
|
}
|
|
388
906
|
async function streamRequest(url, method, options = {}) {
|
|
389
|
-
|
|
390
|
-
|
|
907
|
+
const { headersOptions, methodName } = (() => {
|
|
908
|
+
try {
|
|
909
|
+
if (!baseUrl && !isAbsoluteUrl(url)) {
|
|
910
|
+
throw new TransportError("ctx.http requires an absolute URL when provider.upstream.baseUrl is not declared", { code: "transport_invalid_url" });
|
|
911
|
+
}
|
|
912
|
+
assertNoHttpTransportOverrides(options);
|
|
913
|
+
const redirectPolicy = snapshotHttpRedirectPolicy(options);
|
|
914
|
+
return {
|
|
915
|
+
headersOptions: {
|
|
916
|
+
...withClientHeaders(options, clientOptions, options.body),
|
|
917
|
+
redirectPolicy,
|
|
918
|
+
},
|
|
919
|
+
methodName: normalizeHttpMethod(method),
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
catch (error) {
|
|
923
|
+
throw redactSensitiveRequestError(error, url, options.sensitiveParams);
|
|
924
|
+
}
|
|
925
|
+
})();
|
|
926
|
+
// Streams never retry. Observe the header fetch without consuming its body.
|
|
927
|
+
const telemetry = startHttpTelemetry(scopedHttpTelemetry(clientOptions.httpTelemetry), "off");
|
|
928
|
+
const started = performance.now();
|
|
929
|
+
const alreadyAborted = headersOptions.signal?.aborted === true;
|
|
930
|
+
let proxyUsed = false;
|
|
931
|
+
const record = (event) => {
|
|
932
|
+
if (alreadyAborted)
|
|
933
|
+
return;
|
|
934
|
+
telemetry.recordAttempt({ ...event, ms: performance.now() - started, proxyUsed });
|
|
935
|
+
};
|
|
936
|
+
try {
|
|
937
|
+
const response = await fetchNativeHttpStream(baseUrl, url, methodName, headersOptions, clientOptions, warnOnce, (used) => {
|
|
938
|
+
proxyUsed = used;
|
|
939
|
+
});
|
|
940
|
+
record({ status: response.status });
|
|
941
|
+
return response;
|
|
942
|
+
}
|
|
943
|
+
catch (error) {
|
|
944
|
+
record(httpAttemptFailure(error));
|
|
945
|
+
throw error;
|
|
946
|
+
}
|
|
947
|
+
finally {
|
|
948
|
+
telemetry.finish(performance.now() - started);
|
|
391
949
|
}
|
|
392
|
-
assertNoHttpTransportOverrides(options);
|
|
393
|
-
const headersOptions = withClientHeaders(options, clientOptions, options.body);
|
|
394
|
-
const methodName = normalizeHttpMethod(method);
|
|
395
|
-
return fetchNativeHttpStream(baseUrl, url, methodName, headersOptions, clientOptions, warnOnce);
|
|
396
950
|
}
|
|
397
951
|
return {
|
|
398
952
|
request: async (url, options = {}) => request(url, options.method ?? "GET", options),
|
|
@@ -402,10 +956,15 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
|
|
|
402
956
|
delete: async (url, options) => request(url, "DELETE", options),
|
|
403
957
|
stream: async (url, options = {}) => streamRequest(url, options.method ?? "GET", options),
|
|
404
958
|
sse: async (url, options = {}) => {
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
959
|
+
const callerHeaders = options.headers;
|
|
960
|
+
const headers = typeof callerHeaders === "function"
|
|
961
|
+
? async (attempt) => {
|
|
962
|
+
// Pass a malformed result through unchanged so the factory
|
|
963
|
+
// validation rejects it instead of spreading it into `{}`.
|
|
964
|
+
const minted = await callerHeaders(attempt);
|
|
965
|
+
return isHeaderRecord(minted) ? { Accept: "text/event-stream", ...minted } : minted;
|
|
966
|
+
}
|
|
967
|
+
: { Accept: "text/event-stream", ...callerHeaders };
|
|
409
968
|
const response = await streamRequest(url, options.method ?? "GET", {
|
|
410
969
|
...options,
|
|
411
970
|
headers,
|