mixdog 0.9.63 → 0.9.65
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/README.md +3 -0
- package/package.json +9 -2
- package/scripts/code-graph-description-contract.mjs +3 -3
- package/scripts/verify-release-assets.mjs +2 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +11 -14
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -6
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -2
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +1 -0
- package/src/runtime/agent/orchestrator/config.mjs +19 -124
- package/src/runtime/agent/orchestrator/context/collect.mjs +6 -6
- package/src/runtime/agent/orchestrator/dispatch-persist.mjs +2 -2
- package/src/runtime/agent/orchestrator/internal-agents.mjs +2 -2
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +1 -1
- package/src/runtime/agent/orchestrator/mcp/client.mjs +4 -4
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +4 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +4 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +333 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +3 -277
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +196 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +460 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +14 -593
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +4 -4
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +5 -280
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +357 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-headers.mjs +216 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +2 -188
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/registry.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/compact/budget.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/compact/file-reattach.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +17 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +18 -26
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/delivered-completions.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +260 -0
- package/src/runtime/agent/orchestrator/session/store/serialize.mjs +72 -0
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +163 -0
- package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/store.mjs +9 -561
- package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +11 -11
- package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +2 -154
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/cwd-utils.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/path-locks.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +2 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-open.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +519 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +5 -449
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +212 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +1 -7
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +9 -36
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +15 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +429 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +2 -395
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/mutation-planner.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/next-call-utils.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +7 -7
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +72 -9
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/patch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/result-compression.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +2 -444
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +505 -0
- package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +1 -1
- package/src/runtime/channels/lib/backend-dispatch.mjs +20 -5
- package/src/runtime/channels/lib/scheduler.mjs +1 -1
- package/src/runtime/channels/lib/telegram-format.mjs +1 -1
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +4 -4
- package/src/runtime/channels/tool-defs.mjs +2 -3
- package/src/runtime/memory/index.mjs +1 -5
- package/src/runtime/memory/lib/core-memory-candidates.mjs +357 -0
- package/src/runtime/memory/lib/core-memory-store.mjs +7 -357
- package/src/runtime/memory/lib/cycle-signatures.mjs +1 -1
- package/src/runtime/memory/lib/embedding-model-config.mjs +1 -1
- package/src/runtime/memory/lib/embedding-provider.mjs +1 -5
- package/src/runtime/memory/lib/embedding-worker.mjs +1 -5
- package/src/runtime/memory/lib/ko-morph.mjs +2 -2
- package/src/runtime/memory/lib/memory-config-flags.mjs +2 -5
- package/src/runtime/memory/lib/memory-cycle-requests.mjs +3 -7
- package/src/runtime/memory/lib/memory-cycle1.mjs +4 -8
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +2 -5
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -5
- package/src/runtime/memory/lib/memory-embed.mjs +2 -6
- package/src/runtime/memory/lib/memory-log.mjs +8 -0
- package/src/runtime/memory/lib/memory-ops-policy.mjs +4 -8
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +1 -1
- package/src/runtime/memory/lib/memory-recall-store.mjs +2 -193
- package/src/runtime/memory/lib/memory-score.mjs +1 -1
- package/src/runtime/memory/lib/memory-text-utils.mjs +2 -2
- package/src/runtime/memory/lib/memory.mjs +2 -6
- package/src/runtime/memory/lib/pg/adapter.mjs +2 -6
- package/src/runtime/memory/lib/pg/process.mjs +1 -5
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -5
- package/src/runtime/memory/lib/recall-format.mjs +2 -2
- package/src/runtime/memory/lib/recall-scoring.mjs +195 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +1 -5
- package/src/runtime/memory/lib/trace-store.mjs +2 -6
- package/src/runtime/search/lib/http-fetch.mjs +6 -6
- package/src/runtime/search/lib/web-tools.mjs +2 -2
- package/src/runtime/shared/buffered-appender.mjs +1 -1
- package/src/runtime/shared/child-guardian.mjs +1 -1
- package/src/runtime/shared/child-spawn-gate.mjs +1 -1
- package/src/runtime/shared/config.mjs +4 -4
- package/src/runtime/shared/launcher-control.mjs +9 -10
- package/src/runtime/shared/llm/index.mjs +5 -20
- package/src/runtime/shared/markdown-frontmatter.mjs +1 -1
- package/src/runtime/shared/memory-snapshot.mjs +4 -4
- package/src/runtime/shared/pristine-execution.mjs +1 -1
- package/src/runtime/shared/process-lifecycle.mjs +0 -1
- package/src/runtime/shared/process-shutdown.mjs +1 -1
- package/src/runtime/shared/resource-admission.mjs +2 -2
- package/src/runtime/shared/schedule-session-run.mjs +1 -1
- package/src/runtime/shared/schedules-db.mjs +1 -1
- package/src/runtime/shared/service-discovery.mjs +2 -2
- package/src/runtime/shared/staged-update.mjs +6 -6
- package/src/runtime/shared/tool-execution-contract.mjs +1 -1
- package/src/runtime/shared/tool-primitives.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +2 -2
- package/src/runtime/shared/turn-snapshot.mjs +184 -0
- package/src/runtime/shared/update-checker.mjs +1 -1
- package/src/runtime/shared/user-cwd.mjs +3 -3
- package/src/runtime/shared/user-data-guard.mjs +4 -4
- package/src/runtime/shared/workspace-router.mjs +5 -5
- package/src/session-runtime/config-helpers.mjs +2 -3
- package/src/session-runtime/config-lifecycle.mjs +1 -1
- package/src/session-runtime/hitch-profile.mjs +1 -1
- package/src/session-runtime/lifecycle-api.mjs +6 -4
- package/src/session-runtime/model-recency.mjs +5 -5
- package/src/session-runtime/plugin-mcp.mjs +1 -1
- package/src/session-runtime/provider-request-snapshot.mjs +338 -0
- package/src/session-runtime/runtime-core.mjs +4 -0
- package/src/session-runtime/session-text.mjs +4 -4
- package/src/session-runtime/session-turn-api.mjs +7 -0
- package/src/session-runtime/tool-catalog-schema.mjs +197 -0
- package/src/session-runtime/tool-catalog.mjs +6 -483
- package/src/session-runtime/tool-defs.mjs +1 -1
- package/src/standalone/agent-task-status.mjs +1 -1
- package/src/standalone/agent-tool/helpers.mjs +2 -2
- package/src/standalone/channel-admin.mjs +4 -4
- package/src/standalone/explore-tool.mjs +3 -3
- package/src/standalone/hook-bus/config.mjs +1 -1
- package/src/standalone/hook-bus/constants.mjs +1 -1
- package/src/standalone/hook-bus/rules.mjs +1 -1
- package/src/standalone/projects.mjs +1 -1
- package/src/standalone/provider-admin.mjs +2 -2
- package/src/tui/App.jsx +1 -1
- package/src/tui/app/app-format.mjs +2 -2
- package/src/tui/app/clipboard.mjs +3 -3
- package/src/tui/app/input-parsers.mjs +1 -1
- package/src/tui/app/text-layout.mjs +1 -1
- package/src/tui/app/transcript-row-estimate.mjs +360 -0
- package/src/tui/app/transcript-window.mjs +5 -347
- package/src/tui/app/use-mouse-input.mjs +1 -1
- package/src/tui/app/use-prompt-handlers.mjs +1 -1
- package/src/tui/components/tool-execution/surface-detail.mjs +9 -9
- package/src/tui/components/tool-execution/text-format.mjs +2 -2
- package/src/tui/components/tool-output-format.mjs +7 -7
- package/src/tui/display-width.mjs +4 -4
- package/src/tui/dist/index.mjs +187 -99
- package/src/tui/engine/agent-envelope.mjs +5 -5
- package/src/tui/engine/live-share.mjs +100 -0
- package/src/tui/engine/notification-plan.mjs +1 -1
- package/src/tui/engine/queue-helpers.mjs +3 -3
- package/src/tui/engine/render-timing.mjs +4 -5
- package/src/tui/engine/session-api-ext.mjs +6 -3
- package/src/tui/engine/tool-result-status.mjs +5 -5
- package/src/tui/engine/tool-result-text.mjs +1 -1
- package/src/tui/engine/transcript-spill.mjs +585 -0
- package/src/tui/engine/tui-steering-persist.mjs +1 -1
- package/src/tui/engine.mjs +29 -474
- package/src/tui/lib/voice-setup.mjs +1 -1
- package/src/tui/markdown/format-token.mjs +1 -1
- package/src/tui/markdown/render-ansi.mjs +1 -1
- package/src/tui/markdown/table-layout.mjs +4 -4
- package/src/tui/paste-attachments.mjs +1 -1
- package/src/tui/prompt-history-store.mjs +3 -3
- package/src/tui/statusline-ansi-bridge.mjs +2 -2
- package/src/tui/theme.mjs +1 -1
- package/src/tui/transcript-tool-failures.mjs +2 -2
- package/src/ui/ansi.mjs +0 -9
- package/src/ui/statusline-format.mjs +2 -4
- package/scripts/_bench-cwc.json +0 -20
- package/scripts/_jitter-fuzz.jsx +0 -42
- package/scripts/_jitter-fuzz2.jsx +0 -30
- package/scripts/_jitter-probe.jsx +0 -35
- package/scripts/_jp2.jsx +0 -16
- package/scripts/_smoke_wd.mjs +0 -7
- package/scripts/abort-recovery-test.mjs +0 -175
- package/scripts/agent-dispatch-abort-compose-test.mjs +0 -31
- package/scripts/agent-live-arg-guard-smoke.mjs +0 -20
- package/scripts/agent-live-path-suffix-smoke.mjs +0 -34
- package/scripts/agent-live-toolcall-args-smoke.mjs +0 -45
- package/scripts/agent-loop-policy-test.mjs +0 -37
- package/scripts/agent-model-liveness-test.mjs +0 -763
- package/scripts/agent-parallel-smoke.mjs +0 -435
- package/scripts/agent-route-batch-test.mjs +0 -40
- package/scripts/agent-tag-reuse-smoke.mjs +0 -441
- package/scripts/agent-terminal-reap-test.mjs +0 -252
- package/scripts/agent-trace-io-test.mjs +0 -133
- package/scripts/ansi-color-capability-test.mjs +0 -90
- package/scripts/anthropic-admission-retry-integration-test.mjs +0 -119
- package/scripts/anthropic-maxtokens-test.mjs +0 -119
- package/scripts/anthropic-oauth-refresh-race-test.mjs +0 -397
- package/scripts/anthropic-transport-policy-test.mjs +0 -726
- package/scripts/apply-patch-edit-smoke.mjs +0 -71
- package/scripts/arg-guard-test.mjs +0 -93
- package/scripts/async-notify-settlement-test.mjs +0 -99
- package/scripts/atomic-lock-tryonce-test.mjs +0 -125
- package/scripts/background-task-meta-smoke.mjs +0 -38
- package/scripts/bench-run.mjs +0 -508
- package/scripts/boot-smoke.mjs +0 -137
- package/scripts/build-runtime-windows.ps1 +0 -242
- package/scripts/channel-daemon-smoke.mjs +0 -1103
- package/scripts/code-graph-aggregate-cwd-test.mjs +0 -158
- package/scripts/code-graph-disk-hit-test.mjs +0 -290
- package/scripts/code-graph-dispatch-test.mjs +0 -96
- package/scripts/code-graph-root-federation-test.mjs +0 -273
- package/scripts/compact-active-turn-test.mjs +0 -68
- package/scripts/compact-file-reattach-test.mjs +0 -88
- package/scripts/compact-pressure-test.mjs +0 -788
- package/scripts/compact-prior-context-flatten-test.mjs +0 -252
- package/scripts/compact-recall-digest-test.mjs +0 -57
- package/scripts/compact-smoke.mjs +0 -1017
- package/scripts/compact-trigger-migration-smoke.mjs +0 -261
- package/scripts/compacted-placeholder-scrub-test.mjs +0 -63
- package/scripts/context-mcp-metering-test.mjs +0 -1406
- package/scripts/debounced-skills-async-save-test.mjs +0 -57
- package/scripts/deferred-tool-loading-test.mjs +0 -250
- package/scripts/desktop-session-bridge-test.mjs +0 -1013
- package/scripts/dispatch-persist-recovery-test.mjs +0 -141
- package/scripts/embedding-worker-exit-test.mjs +0 -76
- package/scripts/execution-completion-dedup-test.mjs +0 -205
- package/scripts/execution-pending-resume-kick-test.mjs +0 -151
- package/scripts/execution-resume-esc-integration-test.mjs +0 -176
- package/scripts/explore-bench-tmp.mjs +0 -36
- package/scripts/explore-bench.mjs +0 -248
- package/scripts/explore-prompt-policy-test.mjs +0 -256
- package/scripts/explore-timeout-cancel-test.mjs +0 -345
- package/scripts/find-fuzzy-hidden-test.mjs +0 -267
- package/scripts/forwarder-rebind-tail-test.mjs +0 -67
- package/scripts/freevar-smoke.mjs +0 -98
- package/scripts/gemini-provider-test.mjs +0 -1448
- package/scripts/generate-oc-icons.mjs +0 -11
- package/scripts/grok-oauth-refresh-race-test.mjs +0 -273
- package/scripts/headless-pristine-execution-test.mjs +0 -614
- package/scripts/hook-bus-test.mjs +0 -552
- package/scripts/ingest-pure-conversation-smoke.mjs +0 -175
- package/scripts/internal-comms-bench-test.mjs +0 -226
- package/scripts/internal-comms-bench.mjs +0 -853
- package/scripts/internal-comms-smoke.mjs +0 -277
- package/scripts/internal-tools-normalization-test.mjs +0 -62
- package/scripts/interrupted-turn-history-test.mjs +0 -399
- package/scripts/lead-workflow-smoke.mjs +0 -592
- package/scripts/lifecycle-api-test.mjs +0 -137
- package/scripts/live-share-test.mjs +0 -148
- package/scripts/live-worker-smoke.mjs +0 -333
- package/scripts/log-writer-guard-smoke.mjs +0 -131
- package/scripts/maintenance-default-routes-test.mjs +0 -164
- package/scripts/max-output-recovery-persist-test.mjs +0 -81
- package/scripts/max-output-recovery-test.mjs +0 -368
- package/scripts/mcp-client-normalization-test.mjs +0 -45
- package/scripts/mcp-grace-deferred-test.mjs +0 -225
- package/scripts/memory-core-input-test.mjs +0 -167
- package/scripts/memory-cycle-routing-test.mjs +0 -111
- package/scripts/memory-meta-concurrency-test.mjs +0 -20
- package/scripts/memory-pg-recovery-test.mjs +0 -59
- package/scripts/memory-rule-contract-test.mjs +0 -93
- package/scripts/model-list-sanitize-test.mjs +0 -37
- package/scripts/mouse-tracking-restore-smoke.mjs +0 -107
- package/scripts/native-edit-wire-test.mjs +0 -152
- package/scripts/notify-completion-mirror-test.mjs +0 -73
- package/scripts/openai-oauth-refresh-race-test.mjs +0 -120
- package/scripts/openai-oauth-ws-1006-retry-test.mjs +0 -954
- package/scripts/openai-ws-early-settle-test.mjs +0 -216
- package/scripts/output-style-bench.mjs +0 -292
- package/scripts/output-style-smoke.mjs +0 -145
- package/scripts/parent-abort-link-test.mjs +0 -90
- package/scripts/patch-binary-cache-test.mjs +0 -181
- package/scripts/path-suffix-test.mjs +0 -57
- package/scripts/pending-completion-drop-test.mjs +0 -239
- package/scripts/pending-messages-lock-nonblocking-test.mjs +0 -62
- package/scripts/pretool-ask-runtime-test.mjs +0 -54
- package/scripts/process-lifecycle-test.mjs +0 -461
- package/scripts/prompt-immediate-render-test.mjs +0 -89
- package/scripts/prompt-input-parity-test.mjs +0 -145
- package/scripts/provider-admission-scheduler-test.mjs +0 -681
- package/scripts/provider-contract-test.mjs +0 -632
- package/scripts/provider-stream-stall-test.mjs +0 -276
- package/scripts/provider-toolcall-test.mjs +0 -3063
- package/scripts/reactive-compact-persist-smoke.mjs +0 -203
- package/scripts/recall-bench.mjs +0 -359
- package/scripts/resource-admission-test.mjs +0 -792
- package/scripts/result-classification-test.mjs +0 -75
- package/scripts/rg-runner-test.mjs +0 -240
- package/scripts/routing-corpus-test.mjs +0 -349
- package/scripts/sanitize-tool-pairs-test.mjs +0 -260
- package/scripts/session-bench-cache-break-test.mjs +0 -102
- package/scripts/session-bench.mjs +0 -1708
- package/scripts/session-context-bench.mjs +0 -344
- package/scripts/session-heartbeat-lifecycle-test.mjs +0 -68
- package/scripts/session-ingest-compaction-smoke.mjs +0 -279
- package/scripts/session-ingest-smoke.mjs +0 -177
- package/scripts/session-orphan-sweep-test.mjs +0 -109
- package/scripts/set-effort-config-test.mjs +0 -41
- package/scripts/shell-failure-diagnostics-test.mjs +0 -280
- package/scripts/shell-hardening-test.mjs +0 -209
- package/scripts/shell-jobs-windows-hide-test.mjs +0 -164
- package/scripts/ship-mode-test.mjs +0 -98
- package/scripts/smoke-loop-failure-summary-test.mjs +0 -38
- package/scripts/smoke-loop-failure-summary.mjs +0 -16
- package/scripts/smoke-loop-report.mjs +0 -221
- package/scripts/smoke-loop.mjs +0 -202
- package/scripts/smoke-runtime-negative.ps1 +0 -106
- package/scripts/statusline-quota-hysteresis-test.mjs +0 -62
- package/scripts/steering-drain-buckets-test.mjs +0 -467
- package/scripts/steering-fold-provenance-test.mjs +0 -71
- package/scripts/streaming-tail-window-test.mjs +0 -175
- package/scripts/submit-commandbusy-race-test.mjs +0 -257
- package/scripts/task-bench.mjs +0 -205
- package/scripts/terminal-bench-isolation-guards-test.mjs +0 -102
- package/scripts/termio-input-smoke.mjs +0 -199
- package/scripts/tool-result-hook-test.mjs +0 -48
- package/scripts/tool-smoke.mjs +0 -2453
- package/scripts/tool-tui-presentation-test.mjs +0 -286
- package/scripts/toolcall-args-test.mjs +0 -239
- package/scripts/tui-ambiguous-width-test.mjs +0 -113
- package/scripts/tui-background-failure-smoke.mjs +0 -73
- package/scripts/tui-perf-run.ps1 +0 -26
- package/scripts/tui-render-smoke.mjs +0 -90
- package/scripts/tui-runtime-load-bench-entry.jsx +0 -608
- package/scripts/tui-runtime-load-bench.mjs +0 -50
- package/scripts/tui-store-frame-batch-test.mjs +0 -99
- package/scripts/tui-transcript-jitter-harness-entry.jsx +0 -383
- package/scripts/tui-transcript-perf-test.mjs +0 -934
- package/scripts/usage-metrics-epoch-smoke.mjs +0 -114
- package/scripts/verify-release-assets-test.mjs +0 -281
- package/scripts/web-fetch-routing-test.mjs +0 -158
- package/scripts/webhook-smoke.mjs +0 -201
- package/scripts/windows-hide-spawn-options-test.mjs +0 -19
- package/scripts/worker-notify-rejection-test.mjs +0 -51
- package/scripts/write-backpressure-test.mjs +0 -147
|
@@ -1,726 +0,0 @@
|
|
|
1
|
-
import test from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
ANTHROPIC_RETRY_BACKOFF_MS,
|
|
6
|
-
ANTHROPIC_RETRY_JITTER_RATIO,
|
|
7
|
-
AnthropicFallbackTriggeredError,
|
|
8
|
-
anthropicMaxAttempts,
|
|
9
|
-
anthropicRequestTimeoutMs,
|
|
10
|
-
classifyError,
|
|
11
|
-
classifyMidstreamError,
|
|
12
|
-
MAX_SAFE_TIMEOUT_MS,
|
|
13
|
-
MIDSTREAM_RETRY_POLICY,
|
|
14
|
-
withRetry,
|
|
15
|
-
} from '../src/runtime/agent/orchestrator/providers/retry-classifier.mjs';
|
|
16
|
-
import { _midstreamSleepWithAbort, parseSSEStream } from '../src/runtime/agent/orchestrator/providers/anthropic-sse.mjs';
|
|
17
|
-
import { AnthropicProvider } from '../src/runtime/agent/orchestrator/providers/anthropic.mjs';
|
|
18
|
-
import { AnthropicOAuthProvider } from '../src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs';
|
|
19
|
-
import {
|
|
20
|
-
ANTHROPIC_CACHE_TTL_STABLE,
|
|
21
|
-
ANTHROPIC_CACHE_TTL_VOLATILE,
|
|
22
|
-
resolveAnthropicCacheTtls,
|
|
23
|
-
} from '../src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs';
|
|
24
|
-
|
|
25
|
-
test('Anthropic cache defaults keep system/tier3 stable and messages volatile', () => {
|
|
26
|
-
assert.deepEqual(resolveAnthropicCacheTtls(), {
|
|
27
|
-
tools: null,
|
|
28
|
-
system: ANTHROPIC_CACHE_TTL_STABLE,
|
|
29
|
-
tier3: ANTHROPIC_CACHE_TTL_STABLE,
|
|
30
|
-
messages: ANTHROPIC_CACHE_TTL_VOLATILE,
|
|
31
|
-
});
|
|
32
|
-
const descending = resolveAnthropicCacheTtls({
|
|
33
|
-
cacheStrategy: { system: '5m', tier3: '1h', messages: '1h' },
|
|
34
|
-
});
|
|
35
|
-
assert.equal(descending.system, ANTHROPIC_CACHE_TTL_VOLATILE);
|
|
36
|
-
assert.equal(descending.tier3, ANTHROPIC_CACHE_TTL_VOLATILE);
|
|
37
|
-
assert.equal(descending.messages, ANTHROPIC_CACHE_TTL_VOLATILE);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test('Anthropic transport classifier covers every 5xx, 529, and nested connection errno', () => {
|
|
41
|
-
for (const status of [500, 501, 505, 529, 599]) {
|
|
42
|
-
assert.equal(classifyError({ status }), 'transient');
|
|
43
|
-
}
|
|
44
|
-
assert.equal(classifyError({ code: 'EWRAPPED', cause: { code: 'EUNKNOWN', cause: { code: 'ECONNRESET' } } }), 'transient');
|
|
45
|
-
assert.equal(classifyError({ name: 'APIConnectionError' }), 'transient');
|
|
46
|
-
assert.equal(classifyError({ code: 'TRUNCATED_STREAM', partialToolCall: true }), 'permanent');
|
|
47
|
-
assert.equal(classifyError({ code: 'TRUNCATED_STREAM', emittedThinking: true }), 'permanent');
|
|
48
|
-
assert.equal(classifyError({ status: 403 }), 'auth');
|
|
49
|
-
assert.equal(classifyError({ code: 'TRUNCATED_STREAM', status: 401 }), 'auth');
|
|
50
|
-
assert.equal(classifyError(Object.assign(new Error('canceled'), {
|
|
51
|
-
name: 'AbortError',
|
|
52
|
-
cause: { code: 'ECONNRESET' },
|
|
53
|
-
})), 'permanent');
|
|
54
|
-
assert.equal(classifyError({ status: 418, cause: { code: 'ECONNRESET' } }), 'permanent');
|
|
55
|
-
assert.equal(classifyError({ status: 409, cause: { code: 'EUNKNOWN' } }), 'transient');
|
|
56
|
-
|
|
57
|
-
const sdkAbort = Object.assign(new Error('Request was aborted.'), {
|
|
58
|
-
cause: { code: 'ECONNRESET' },
|
|
59
|
-
});
|
|
60
|
-
Object.defineProperty(sdkAbort, 'constructor', { value: { name: 'APIUserAbortError' } });
|
|
61
|
-
assert.equal(sdkAbort.name, 'Error');
|
|
62
|
-
assert.equal(classifyError(sdkAbort), 'permanent');
|
|
63
|
-
assert.equal(classifyError({
|
|
64
|
-
name: 'Error',
|
|
65
|
-
type: 'api_user_abort_error',
|
|
66
|
-
cause: { code: 'ECONNRESET' },
|
|
67
|
-
}), 'permanent');
|
|
68
|
-
assert.equal(classifyError({
|
|
69
|
-
name: 'Error',
|
|
70
|
-
message: 'Request was aborted.',
|
|
71
|
-
cause: { code: 'ECONNRESET' },
|
|
72
|
-
}), 'transient', 'abort-like message alone is not an SDK type marker');
|
|
73
|
-
|
|
74
|
-
const cyclicA = { code: 'EWRAPPED' };
|
|
75
|
-
const cyclicB = { code: 'EUNKNOWN', cause: cyclicA };
|
|
76
|
-
cyclicA.cause = cyclicB;
|
|
77
|
-
assert.equal(classifyError(cyclicA), 'unknown');
|
|
78
|
-
cyclicB.code = 'ECONNRESET';
|
|
79
|
-
assert.equal(classifyError(cyclicA), 'transient');
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test('API-key Anthropic reloads typed 401 once while ordinary 403 is terminal', async () => {
|
|
83
|
-
const provider401 = Object.create(AnthropicProvider.prototype);
|
|
84
|
-
let attempts401 = 0;
|
|
85
|
-
let reloads401 = 0;
|
|
86
|
-
provider401._doSend = async () => {
|
|
87
|
-
attempts401 += 1;
|
|
88
|
-
if (attempts401 === 1) throw Object.assign(new Error('typed auth'), { status: 401 });
|
|
89
|
-
return 'ok';
|
|
90
|
-
};
|
|
91
|
-
provider401.reloadApiKey = () => { reloads401 += 1; };
|
|
92
|
-
assert.equal(await provider401.send([], 'claude', [], {}), 'ok');
|
|
93
|
-
assert.equal(attempts401, 2);
|
|
94
|
-
assert.equal(reloads401, 1);
|
|
95
|
-
|
|
96
|
-
const provider403 = Object.create(AnthropicProvider.prototype);
|
|
97
|
-
let attempts403 = 0;
|
|
98
|
-
let reloads403 = 0;
|
|
99
|
-
const forbidden = Object.assign(new Error('ordinary forbidden'), { status: 403 });
|
|
100
|
-
provider403._doSend = async () => { attempts403 += 1; throw forbidden; };
|
|
101
|
-
provider403.reloadApiKey = () => { reloads403 += 1; };
|
|
102
|
-
await assert.rejects(provider403.send([], 'claude', [], {}), (err) => err === forbidden);
|
|
103
|
-
assert.equal(attempts403, 1);
|
|
104
|
-
assert.equal(reloads403, 0);
|
|
105
|
-
|
|
106
|
-
for (const marker of ['partialToolCall', 'emittedThinking']) {
|
|
107
|
-
const provider = Object.create(AnthropicProvider.prototype);
|
|
108
|
-
let attempts = 0;
|
|
109
|
-
let reloads = 0;
|
|
110
|
-
const exposed401 = Object.assign(new Error(`401 after ${marker}`), {
|
|
111
|
-
status: 401,
|
|
112
|
-
[marker]: true,
|
|
113
|
-
});
|
|
114
|
-
provider._doSend = async () => { attempts += 1; throw exposed401; };
|
|
115
|
-
provider.reloadApiKey = () => { reloads += 1; };
|
|
116
|
-
await assert.rejects(provider.send([], 'claude', [], {}), (err) => err === exposed401);
|
|
117
|
-
assert.equal(attempts, 1, `${marker} must prevent 401 replay`);
|
|
118
|
-
assert.equal(reloads, 0, `${marker} must prevent key reload`);
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test('permanent quota 429 is not retried by the generic rate-limit path', async () => {
|
|
123
|
-
let attempts = 0;
|
|
124
|
-
const quota = new Error('request failed: insufficient_quota');
|
|
125
|
-
await assert.rejects(withRetry(async () => {
|
|
126
|
-
attempts += 1;
|
|
127
|
-
throw quota;
|
|
128
|
-
}, { maxAttempts: 3, backoffMs: [0], retryJitterRatio: 0 }), (err) => err === quota);
|
|
129
|
-
assert.equal(attempts, 1);
|
|
130
|
-
assert.equal(classifyError(quota), 'permanent');
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test('OAuth subscription 429 fails immediately without honoring Retry-After', async () => {
|
|
134
|
-
const oauth = Object.create(AnthropicOAuthProvider.prototype);
|
|
135
|
-
oauth.credentials = { accessToken: 'fixture', expiresAt: Date.now() + 60_000 };
|
|
136
|
-
oauth.config = {};
|
|
137
|
-
oauth.fastModeBetaHeaderLatched = false;
|
|
138
|
-
oauth.ensureAuth = async () => oauth.credentials;
|
|
139
|
-
oauth.scrubTokens = (text) => text;
|
|
140
|
-
let attempts = 0;
|
|
141
|
-
const limited = {
|
|
142
|
-
status: 429,
|
|
143
|
-
ok: false,
|
|
144
|
-
headers: new Map([['retry-after', '14400']]),
|
|
145
|
-
async text() { return 'subscription rate limit'; },
|
|
146
|
-
};
|
|
147
|
-
await assert.rejects(oauth.send([], 'claude-sonnet-4-6', [], {
|
|
148
|
-
_doRequestFn: async () => {
|
|
149
|
-
attempts += 1;
|
|
150
|
-
return {
|
|
151
|
-
response: limited,
|
|
152
|
-
controller: { abort() {} },
|
|
153
|
-
cancelHandler: null,
|
|
154
|
-
};
|
|
155
|
-
},
|
|
156
|
-
}), (err) => err?.status === 429 && err?.retryAfterMs === 14_400_000);
|
|
157
|
-
assert.equal(attempts, 1);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
test('structured and nested permanent quota codes are terminal; rate-limit codes retry', async () => {
|
|
161
|
-
const terminalCases = [
|
|
162
|
-
Object.assign(new Error('generic 429'), { status: 429, code: 'insufficient_quota' }),
|
|
163
|
-
Object.assign(new Error('generic 429'), { status: 429, error: { code: 'quota_exceeded' } }),
|
|
164
|
-
Object.assign(new Error('generic 429'), {
|
|
165
|
-
status: 429,
|
|
166
|
-
cause: { code: 'EWRAPPED', cause: { error: { code: 'resource_exhausted' } } },
|
|
167
|
-
}),
|
|
168
|
-
];
|
|
169
|
-
for (const quota of terminalCases) {
|
|
170
|
-
let attempts = 0;
|
|
171
|
-
await assert.rejects(withRetry(async () => {
|
|
172
|
-
attempts += 1;
|
|
173
|
-
throw quota;
|
|
174
|
-
}, { maxAttempts: 2, backoffMs: [0], retryJitterRatio: 0 }), (err) => err === quota);
|
|
175
|
-
assert.equal(attempts, 1);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
for (const code of ['rate_limit_error', 'rate_limit_exceeded']) {
|
|
179
|
-
let attempts = 0;
|
|
180
|
-
const result = await withRetry(async () => {
|
|
181
|
-
attempts += 1;
|
|
182
|
-
if (attempts === 1) throw Object.assign(new Error('generic 429'), { status: 429, code });
|
|
183
|
-
return 'recovered';
|
|
184
|
-
}, { maxAttempts: 2, backoffMs: [0], retryJitterRatio: 0 });
|
|
185
|
-
assert.equal(result, 'recovered');
|
|
186
|
-
assert.equal(attempts, 2);
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test('partial tool/thinking markers veto every request retry path, including 429', async () => {
|
|
191
|
-
for (const status of [429, 529]) {
|
|
192
|
-
for (const marker of ['partialToolCall', 'emittedThinking']) {
|
|
193
|
-
let attempts = 0;
|
|
194
|
-
const exposed = Object.assign(new Error(`${status} after ${marker}`), {
|
|
195
|
-
status,
|
|
196
|
-
[marker]: true,
|
|
197
|
-
});
|
|
198
|
-
await assert.rejects(withRetry(async () => {
|
|
199
|
-
attempts += 1;
|
|
200
|
-
throw exposed;
|
|
201
|
-
}, {
|
|
202
|
-
maxAttempts: 4,
|
|
203
|
-
backoffMs: [0],
|
|
204
|
-
retryJitterRatio: 0,
|
|
205
|
-
model: 'claude-opus-primary',
|
|
206
|
-
fallbackModel: 'claude-sonnet-fallback',
|
|
207
|
-
}), (err) => err === exposed);
|
|
208
|
-
assert.equal(attempts, 1, `${status}/${marker} must not replay`);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
test('optional fallback triggers on the third 529 and never when absent', async () => {
|
|
214
|
-
let attempts = 0;
|
|
215
|
-
await assert.rejects(withRetry(async () => {
|
|
216
|
-
attempts += 1;
|
|
217
|
-
throw Object.assign(new Error('busy'), { status: 529, httpStatus: 529 });
|
|
218
|
-
}, {
|
|
219
|
-
maxAttempts: 5,
|
|
220
|
-
backoffMs: [0],
|
|
221
|
-
retryJitterRatio: 0,
|
|
222
|
-
model: 'claude-opus-primary',
|
|
223
|
-
fallbackModel: 'claude-sonnet-fallback',
|
|
224
|
-
}), (err) => (
|
|
225
|
-
err instanceof AnthropicFallbackTriggeredError
|
|
226
|
-
&& err.originalModel === 'claude-opus-primary'
|
|
227
|
-
&& err.fallbackModel === 'claude-sonnet-fallback'
|
|
228
|
-
));
|
|
229
|
-
assert.equal(attempts, 3);
|
|
230
|
-
|
|
231
|
-
attempts = 0;
|
|
232
|
-
await assert.rejects(withRetry(async () => {
|
|
233
|
-
attempts += 1;
|
|
234
|
-
throw Object.assign(new Error('overloaded without fallback'), { status: 529 });
|
|
235
|
-
}, {
|
|
236
|
-
maxAttempts: 4,
|
|
237
|
-
backoffMs: [0],
|
|
238
|
-
retryJitterRatio: 0,
|
|
239
|
-
model: 'claude-opus-primary',
|
|
240
|
-
}), /without fallback/);
|
|
241
|
-
assert.equal(attempts, 4);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
function successfulAnthropicResponse(content = 'fallback-ok') {
|
|
245
|
-
const bytes = new TextEncoder().encode([
|
|
246
|
-
{ type: 'message_start', message: { model: 'claude-sonnet-fallback', usage: { input_tokens: 1 } } },
|
|
247
|
-
{ type: 'content_block_start', index: 0, content_block: { type: 'text' } },
|
|
248
|
-
{ type: 'content_block_delta', index: 0, delta: { type: 'text_delta', text: content } },
|
|
249
|
-
{ type: 'content_block_stop', index: 0 },
|
|
250
|
-
{ type: 'message_delta', delta: { stop_reason: 'end_turn' }, usage: { output_tokens: 1 } },
|
|
251
|
-
{ type: 'message_stop' },
|
|
252
|
-
].map((event) => `event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`).join(''));
|
|
253
|
-
let sent = false;
|
|
254
|
-
return {
|
|
255
|
-
ok: true,
|
|
256
|
-
status: 200,
|
|
257
|
-
headers: new Map(),
|
|
258
|
-
body: { getReader: () => ({
|
|
259
|
-
async read() {
|
|
260
|
-
if (sent) return { done: true };
|
|
261
|
-
sent = true;
|
|
262
|
-
return { done: false, value: bytes };
|
|
263
|
-
},
|
|
264
|
-
async cancel() {},
|
|
265
|
-
releaseLock() {},
|
|
266
|
-
}) },
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function nonStreamingAnthropicMessage(content = 'replacement') {
|
|
271
|
-
return {
|
|
272
|
-
id: 'msg_fixture',
|
|
273
|
-
model: 'claude-sonnet-4-6',
|
|
274
|
-
content: [{ type: 'text', text: content }],
|
|
275
|
-
stop_reason: 'end_turn',
|
|
276
|
-
usage: { input_tokens: 2, output_tokens: 1 },
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
test('session plumbing fail-closes stream replacement and preserves unique output', { concurrency: false }, async (t) => {
|
|
281
|
-
const priorRetries = process.env.CLAUDE_CODE_MAX_RETRIES;
|
|
282
|
-
process.env.CLAUDE_CODE_MAX_RETRIES = '0';
|
|
283
|
-
try {
|
|
284
|
-
process.env.MIXDOG_AGENT_TRACE_DISABLE = '1';
|
|
285
|
-
const { initProviders, getProvider } = await import('../src/runtime/agent/orchestrator/providers/registry.mjs');
|
|
286
|
-
await initProviders({ anthropic: { enabled: true, apiKey: 'fixture-key' } });
|
|
287
|
-
const provider = getProvider('anthropic');
|
|
288
|
-
const {
|
|
289
|
-
abortSessionTurn,
|
|
290
|
-
askSession,
|
|
291
|
-
createSession,
|
|
292
|
-
getSession,
|
|
293
|
-
} = await import('../src/runtime/agent/orchestrator/session/manager.mjs');
|
|
294
|
-
|
|
295
|
-
const makeDroppedResponse = () => {
|
|
296
|
-
const partial = new TextEncoder().encode([
|
|
297
|
-
{ type: 'message_start', message: { model: 'claude-sonnet-4-6', usage: { input_tokens: 1 } } },
|
|
298
|
-
{ type: 'content_block_start', index: 0, content_block: { type: 'text' } },
|
|
299
|
-
{ type: 'content_block_delta', index: 0, delta: { type: 'text_delta', text: 'partial' } },
|
|
300
|
-
].map((event) => `event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`).join(''));
|
|
301
|
-
let reads = 0;
|
|
302
|
-
return {
|
|
303
|
-
ok: true,
|
|
304
|
-
status: 200,
|
|
305
|
-
headers: new Map(),
|
|
306
|
-
body: { getReader: () => ({
|
|
307
|
-
async read() {
|
|
308
|
-
if (reads++ === 0) return { done: false, value: partial };
|
|
309
|
-
throw Object.assign(new Error('body stream terminated'), { code: 'ECONNRESET' });
|
|
310
|
-
},
|
|
311
|
-
async cancel() {},
|
|
312
|
-
releaseLock() {},
|
|
313
|
-
}) },
|
|
314
|
-
};
|
|
315
|
-
};
|
|
316
|
-
const createTestSession = () => createSession({
|
|
317
|
-
provider: 'anthropic',
|
|
318
|
-
model: 'claude-sonnet-4-6',
|
|
319
|
-
tools: [],
|
|
320
|
-
cwd: process.cwd(),
|
|
321
|
-
skipAgentRules: true,
|
|
322
|
-
skipSkills: true,
|
|
323
|
-
compaction: { auto: false },
|
|
324
|
-
});
|
|
325
|
-
const partialsInHistory = (session) => session.messages.filter((message) => (
|
|
326
|
-
message.role === 'assistant' && message.content === 'partial'
|
|
327
|
-
)).length;
|
|
328
|
-
const runCase = async ({ reset, restartFails = false, cancelRestart = false }) => {
|
|
329
|
-
const session = createTestSession();
|
|
330
|
-
let calls = 0;
|
|
331
|
-
let visible = '';
|
|
332
|
-
let markRestartEntered;
|
|
333
|
-
const restartEntered = new Promise((resolve) => { markRestartEntered = resolve; });
|
|
334
|
-
provider.client = { messages: { create(params, requestOptions = {}) {
|
|
335
|
-
calls += 1;
|
|
336
|
-
if (params.stream === false) {
|
|
337
|
-
if (restartFails) throw new Error('non-streaming restart failed');
|
|
338
|
-
if (cancelRestart) {
|
|
339
|
-
markRestartEntered();
|
|
340
|
-
return new Promise((_resolve, reject) => {
|
|
341
|
-
const signal = requestOptions.signal;
|
|
342
|
-
const rejectFromAbort = () => reject(
|
|
343
|
-
signal?.reason instanceof Error
|
|
344
|
-
? signal.reason
|
|
345
|
-
: new Error('non-streaming restart canceled'),
|
|
346
|
-
);
|
|
347
|
-
if (signal?.aborted) rejectFromAbort();
|
|
348
|
-
else signal?.addEventListener('abort', rejectFromAbort, { once: true });
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
return Promise.resolve(nonStreamingAnthropicMessage('replacement'));
|
|
352
|
-
}
|
|
353
|
-
return { asResponse: async () => makeDroppedResponse() };
|
|
354
|
-
} } };
|
|
355
|
-
const options = {
|
|
356
|
-
onTextDelta: (chunk) => { visible += chunk; },
|
|
357
|
-
...(reset ? {
|
|
358
|
-
onTextReset: async ({ chars }) => reset({
|
|
359
|
-
chars,
|
|
360
|
-
getVisible: () => visible,
|
|
361
|
-
setVisible: (value) => { visible = value; },
|
|
362
|
-
}),
|
|
363
|
-
} : {}),
|
|
364
|
-
};
|
|
365
|
-
const asking = askSession(
|
|
366
|
-
session.id,
|
|
367
|
-
'fixture prompt',
|
|
368
|
-
null,
|
|
369
|
-
null,
|
|
370
|
-
process.cwd(),
|
|
371
|
-
null,
|
|
372
|
-
options,
|
|
373
|
-
);
|
|
374
|
-
if (cancelRestart) {
|
|
375
|
-
await restartEntered;
|
|
376
|
-
assert.equal(abortSessionTurn(session.id, 'user-cancel'), true);
|
|
377
|
-
}
|
|
378
|
-
const outcome = await asking.then(
|
|
379
|
-
(result) => ({ result }),
|
|
380
|
-
(error) => ({ error }),
|
|
381
|
-
);
|
|
382
|
-
return {
|
|
383
|
-
...outcome,
|
|
384
|
-
calls,
|
|
385
|
-
visible,
|
|
386
|
-
session: getSession(session.id),
|
|
387
|
-
};
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
for (const [name, reset] of [
|
|
391
|
-
['absent acknowledgment', null],
|
|
392
|
-
['false acknowledgment', async () => false],
|
|
393
|
-
['throwing acknowledgment', async () => { throw new Error('reset failed'); }],
|
|
394
|
-
]) {
|
|
395
|
-
await t.test(name, async () => {
|
|
396
|
-
const outcome = await runCase({ reset });
|
|
397
|
-
assert.match(outcome.error?.message || '', /body stream terminated/);
|
|
398
|
-
assert.equal(outcome.calls, 1, 'must not start non-streaming request');
|
|
399
|
-
assert.equal(outcome.visible, 'partial');
|
|
400
|
-
assert.equal(partialsInHistory(outcome.session), 1);
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
await t.test('late positive acknowledgment is awaited before one replacement', async () => {
|
|
405
|
-
const outcome = await runCase({
|
|
406
|
-
reset: async ({ chars, getVisible, setVisible }) => {
|
|
407
|
-
await new Promise((resolve) => setTimeout(resolve, 15));
|
|
408
|
-
setVisible(getVisible().slice(0, -chars));
|
|
409
|
-
return true;
|
|
410
|
-
},
|
|
411
|
-
});
|
|
412
|
-
assert.equal(outcome.error, undefined);
|
|
413
|
-
assert.equal(outcome.calls, 2);
|
|
414
|
-
assert.equal(outcome.visible, '');
|
|
415
|
-
assert.equal(outcome.result.content, 'replacement');
|
|
416
|
-
assert.equal(outcome.session.messages.filter((message) => (
|
|
417
|
-
message.role === 'assistant' && message.content === 'replacement'
|
|
418
|
-
)).length, 1);
|
|
419
|
-
assert.equal(partialsInHistory(outcome.session), 0);
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
await t.test('failed restart restores one partial to interruption history', async () => {
|
|
423
|
-
const outcome = await runCase({
|
|
424
|
-
restartFails: true,
|
|
425
|
-
reset: async ({ chars, getVisible, setVisible }) => {
|
|
426
|
-
setVisible(getVisible().slice(0, -chars));
|
|
427
|
-
return true;
|
|
428
|
-
},
|
|
429
|
-
});
|
|
430
|
-
assert.match(outcome.error?.message || '', /non-streaming restart failed/);
|
|
431
|
-
assert.equal(outcome.calls, 2);
|
|
432
|
-
assert.equal(outcome.visible, '', 'acknowledged consumer keeps the tombstone');
|
|
433
|
-
assert.equal(partialsInHistory(outcome.session), 1, 'history restores exactly one exposed partial');
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
await t.test('cancellation during restart restores the partial before finalization', async () => {
|
|
437
|
-
const outcome = await runCase({
|
|
438
|
-
cancelRestart: true,
|
|
439
|
-
reset: async ({ chars, getVisible, setVisible }) => {
|
|
440
|
-
setVisible(getVisible().slice(0, -chars));
|
|
441
|
-
return true;
|
|
442
|
-
},
|
|
443
|
-
});
|
|
444
|
-
assert.equal(outcome.error?.name, 'SessionClosedError');
|
|
445
|
-
assert.equal(outcome.calls, 2);
|
|
446
|
-
assert.equal(outcome.visible, '', 'acknowledged consumer keeps the tombstone');
|
|
447
|
-
assert.equal(partialsInHistory(outcome.session), 1, 'cancel history preserves exactly one partial');
|
|
448
|
-
});
|
|
449
|
-
} finally {
|
|
450
|
-
if (priorRetries == null) delete process.env.CLAUDE_CODE_MAX_RETRIES;
|
|
451
|
-
else process.env.CLAUDE_CODE_MAX_RETRIES = priorRetries;
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
test('OAuth and API-key providers switch to the opt-in fallback after three 529s', async () => {
|
|
456
|
-
const priorRetries = process.env.CLAUDE_CODE_MAX_RETRIES;
|
|
457
|
-
process.env.CLAUDE_CODE_MAX_RETRIES = '2';
|
|
458
|
-
try {
|
|
459
|
-
const oauth = Object.create(AnthropicOAuthProvider.prototype);
|
|
460
|
-
oauth.credentials = { accessToken: 'fixture', expiresAt: Date.now() + 60_000 };
|
|
461
|
-
oauth.config = {};
|
|
462
|
-
oauth.fastModeBetaHeaderLatched = false;
|
|
463
|
-
oauth.ensureAuth = async () => oauth.credentials;
|
|
464
|
-
const oauthModels = [];
|
|
465
|
-
const oauthSend = AnthropicOAuthProvider.prototype.send;
|
|
466
|
-
oauth.send = function (messages, model, tools, opts) {
|
|
467
|
-
oauthModels.push(model);
|
|
468
|
-
return oauthSend.call(this, messages, model, tools, opts);
|
|
469
|
-
};
|
|
470
|
-
let oauthAttempts = 0;
|
|
471
|
-
const oauthResult = await oauth.send([], 'claude-opus-primary', [], {
|
|
472
|
-
fallbackModel: 'claude-sonnet-fallback',
|
|
473
|
-
_doRequestFn: async () => {
|
|
474
|
-
oauthAttempts += 1;
|
|
475
|
-
const ok = oauthAttempts > 3;
|
|
476
|
-
return {
|
|
477
|
-
response: {
|
|
478
|
-
status: ok ? 200 : 529,
|
|
479
|
-
ok,
|
|
480
|
-
headers: new Map([['retry-after', '0']]),
|
|
481
|
-
async text() { return ''; },
|
|
482
|
-
},
|
|
483
|
-
controller: { abort() {} },
|
|
484
|
-
cancelHandler: null,
|
|
485
|
-
};
|
|
486
|
-
},
|
|
487
|
-
_parseSSEFn: async () => ({
|
|
488
|
-
content: 'oauth-fallback-ok',
|
|
489
|
-
model: 'claude-sonnet-fallback',
|
|
490
|
-
usage: {},
|
|
491
|
-
}),
|
|
492
|
-
});
|
|
493
|
-
assert.equal(oauthResult.content, 'oauth-fallback-ok');
|
|
494
|
-
assert.equal(oauthAttempts, 4);
|
|
495
|
-
assert.deepEqual(oauthModels, ['claude-opus-primary', 'claude-sonnet-fallback']);
|
|
496
|
-
|
|
497
|
-
const direct = Object.create(AnthropicProvider.prototype);
|
|
498
|
-
direct.name = 'anthropic';
|
|
499
|
-
direct.config = {};
|
|
500
|
-
direct.fastModeBetaHeaderLatched = false;
|
|
501
|
-
const directModels = [];
|
|
502
|
-
const directDoSend = AnthropicProvider.prototype._doSend;
|
|
503
|
-
direct._doSend = function (messages, model, tools, opts) {
|
|
504
|
-
directModels.push(model);
|
|
505
|
-
return directDoSend.call(this, messages, model, tools, opts);
|
|
506
|
-
};
|
|
507
|
-
let directAttempts = 0;
|
|
508
|
-
direct.client = { messages: { create() {
|
|
509
|
-
directAttempts += 1;
|
|
510
|
-
return { async asResponse() {
|
|
511
|
-
if (directAttempts <= 3) {
|
|
512
|
-
return { ok: false, status: 529, headers: new Map([['retry-after', '0']]), async text() { return ''; } };
|
|
513
|
-
}
|
|
514
|
-
return successfulAnthropicResponse('direct-fallback-ok');
|
|
515
|
-
} };
|
|
516
|
-
} } };
|
|
517
|
-
const directResult = await direct._doSend([], 'claude-opus-primary', [], {
|
|
518
|
-
fallbackModel: 'claude-sonnet-fallback',
|
|
519
|
-
});
|
|
520
|
-
assert.equal(directResult.content, 'direct-fallback-ok');
|
|
521
|
-
assert.equal(directAttempts, 4);
|
|
522
|
-
assert.deepEqual(directModels, ['claude-opus-primary', 'claude-sonnet-fallback']);
|
|
523
|
-
} finally {
|
|
524
|
-
if (priorRetries == null) delete process.env.CLAUDE_CODE_MAX_RETRIES;
|
|
525
|
-
else process.env.CLAUDE_CODE_MAX_RETRIES = priorRetries;
|
|
526
|
-
}
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
test('Anthropic retry budget defaults to ten retries and is configurable/bounded', () => {
|
|
530
|
-
const prior = process.env.CLAUDE_CODE_MAX_RETRIES;
|
|
531
|
-
try {
|
|
532
|
-
delete process.env.CLAUDE_CODE_MAX_RETRIES;
|
|
533
|
-
assert.equal(anthropicMaxAttempts(), 11);
|
|
534
|
-
process.env.CLAUDE_CODE_MAX_RETRIES = '2';
|
|
535
|
-
assert.equal(anthropicMaxAttempts(), 3);
|
|
536
|
-
process.env.CLAUDE_CODE_MAX_RETRIES = '9999';
|
|
537
|
-
assert.equal(anthropicMaxAttempts(), 101);
|
|
538
|
-
} finally {
|
|
539
|
-
if (prior == null) delete process.env.CLAUDE_CODE_MAX_RETRIES;
|
|
540
|
-
else process.env.CLAUDE_CODE_MAX_RETRIES = prior;
|
|
541
|
-
}
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
test('Anthropic request timeout and exponential backoff match Claude Code defaults', async () => {
|
|
545
|
-
const priorTimeout = process.env.API_TIMEOUT_MS;
|
|
546
|
-
const priorRandom = Math.random;
|
|
547
|
-
try {
|
|
548
|
-
delete process.env.API_TIMEOUT_MS;
|
|
549
|
-
assert.equal(anthropicRequestTimeoutMs(), 600_000);
|
|
550
|
-
process.env.API_TIMEOUT_MS = '123456';
|
|
551
|
-
assert.equal(anthropicRequestTimeoutMs(), 123456);
|
|
552
|
-
|
|
553
|
-
Math.random = () => 0;
|
|
554
|
-
const waits = [];
|
|
555
|
-
let attempts = 0;
|
|
556
|
-
await assert.rejects(withRetry(async () => {
|
|
557
|
-
attempts += 1;
|
|
558
|
-
throw Object.assign(new Error('overloaded'), { status: 529 });
|
|
559
|
-
}, {
|
|
560
|
-
maxAttempts: 4,
|
|
561
|
-
backoffMs: ANTHROPIC_RETRY_BACKOFF_MS,
|
|
562
|
-
retryJitterRatio: ANTHROPIC_RETRY_JITTER_RATIO,
|
|
563
|
-
retryJitterMode: 'positive',
|
|
564
|
-
sleepFn: async (ms) => { waits.push(ms); },
|
|
565
|
-
}), /overloaded/);
|
|
566
|
-
assert.equal(attempts, 4);
|
|
567
|
-
assert.deepEqual(waits, [500, 1000, 2000]);
|
|
568
|
-
} finally {
|
|
569
|
-
Math.random = priorRandom;
|
|
570
|
-
if (priorTimeout == null) delete process.env.API_TIMEOUT_MS;
|
|
571
|
-
else process.env.API_TIMEOUT_MS = priorTimeout;
|
|
572
|
-
}
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
test('x-should-retry false vetoes retry before status defaults', async () => {
|
|
576
|
-
for (const status of [429, 529]) {
|
|
577
|
-
let attempts = 0;
|
|
578
|
-
const denied = Object.assign(new Error(`denied ${status}`), {
|
|
579
|
-
status,
|
|
580
|
-
headers: new Map([['x-should-retry', 'false'], ['retry-after', '0']]),
|
|
581
|
-
});
|
|
582
|
-
await assert.rejects(withRetry(async () => {
|
|
583
|
-
attempts += 1;
|
|
584
|
-
throw denied;
|
|
585
|
-
}, {
|
|
586
|
-
maxAttempts: 3,
|
|
587
|
-
backoffMs: [0],
|
|
588
|
-
retryJitterRatio: 0,
|
|
589
|
-
}), (err) => err === denied);
|
|
590
|
-
assert.equal(attempts, 1);
|
|
591
|
-
}
|
|
592
|
-
});
|
|
593
|
-
|
|
594
|
-
test('Anthropic x-should-retry true overrides a normally terminal status only for Anthropic', async () => {
|
|
595
|
-
for (const provider of ['anthropic', 'gemini']) {
|
|
596
|
-
let attempts = 0;
|
|
597
|
-
const result = await withRetry(async () => {
|
|
598
|
-
attempts += 1;
|
|
599
|
-
if (attempts === 1) {
|
|
600
|
-
throw Object.assign(new Error('bad request with server override'), {
|
|
601
|
-
status: 400,
|
|
602
|
-
headers: new Map([['x-should-retry', 'true']]),
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
return 'recovered';
|
|
606
|
-
}, {
|
|
607
|
-
provider,
|
|
608
|
-
maxAttempts: 2,
|
|
609
|
-
backoffMs: [0],
|
|
610
|
-
retryJitterRatio: 0,
|
|
611
|
-
}).then((value) => value, (error) => error);
|
|
612
|
-
if (provider === 'anthropic') {
|
|
613
|
-
assert.equal(result, 'recovered');
|
|
614
|
-
assert.equal(attempts, 2);
|
|
615
|
-
} else {
|
|
616
|
-
assert.equal(result?.status, 400);
|
|
617
|
-
assert.equal(attempts, 1);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
});
|
|
621
|
-
|
|
622
|
-
test('Retry-After is not capped or jittered and remains abortable', async () => {
|
|
623
|
-
const ac = new AbortController();
|
|
624
|
-
let observed;
|
|
625
|
-
let calls = 0;
|
|
626
|
-
await assert.rejects(withRetry(async () => {
|
|
627
|
-
calls++;
|
|
628
|
-
const err = Object.assign(new Error('busy'), {
|
|
629
|
-
status: 529,
|
|
630
|
-
headers: new Map([['retry-after-ms', '123456']]),
|
|
631
|
-
});
|
|
632
|
-
throw err;
|
|
633
|
-
}, {
|
|
634
|
-
maxAttempts: 2,
|
|
635
|
-
signal: ac.signal,
|
|
636
|
-
maxRetryAfterMs: 1,
|
|
637
|
-
retryJitterRatio: 1,
|
|
638
|
-
onRetry: ({ delayMs }) => {
|
|
639
|
-
observed = delayMs;
|
|
640
|
-
ac.abort(new Error('test stop'));
|
|
641
|
-
},
|
|
642
|
-
}), /test stop/);
|
|
643
|
-
assert.equal(calls, 1);
|
|
644
|
-
assert.equal(observed, 123456);
|
|
645
|
-
});
|
|
646
|
-
|
|
647
|
-
test('oversized HTTP Retry-After is chunked without Node timeout clamping', async () => {
|
|
648
|
-
const chunks = [];
|
|
649
|
-
let attempts = 0;
|
|
650
|
-
const result = await withRetry(async () => {
|
|
651
|
-
attempts += 1;
|
|
652
|
-
if (attempts === 1) {
|
|
653
|
-
throw Object.assign(new Error('busy'), {
|
|
654
|
-
status: 429,
|
|
655
|
-
headers: new Map([['retry-after-ms', '2147483648']]),
|
|
656
|
-
});
|
|
657
|
-
}
|
|
658
|
-
return 'ok';
|
|
659
|
-
}, {
|
|
660
|
-
maxAttempts: 2,
|
|
661
|
-
retryJitterRatio: 1,
|
|
662
|
-
sleepFn: async (ms) => { chunks.push(ms); },
|
|
663
|
-
});
|
|
664
|
-
assert.equal(result, 'ok');
|
|
665
|
-
assert.deepEqual(chunks, [MAX_SAFE_TIMEOUT_MS, 1]);
|
|
666
|
-
});
|
|
667
|
-
|
|
668
|
-
test('oversized Anthropic SSE Retry-After sleep uses the same safe chunks', async () => {
|
|
669
|
-
const chunks = [];
|
|
670
|
-
await _midstreamSleepWithAbort(
|
|
671
|
-
2_147_483_648,
|
|
672
|
-
null,
|
|
673
|
-
async (ms) => { chunks.push(ms); },
|
|
674
|
-
);
|
|
675
|
-
assert.deepEqual(chunks, [MAX_SAFE_TIMEOUT_MS, 1]);
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
test('SSE retries 429/5xx before output regardless of message_start, but not partial output', () => {
|
|
679
|
-
const policy = { mode: 'sse', defaultRetries: 3, perClassifierGate: false };
|
|
680
|
-
const clean = { attemptIndex: 0, sawCompleted: false, sawMessageStart: false };
|
|
681
|
-
assert.equal(classifyMidstreamError({ status: 429 }, clean, policy), 'http_429');
|
|
682
|
-
assert.equal(classifyMidstreamError({ status: 529 }, { ...clean, sawMessageStart: true }, policy), 'http_529');
|
|
683
|
-
for (const exposed of ['emittedText', 'emittedToolCall', 'partialToolCall', 'emittedThinking']) {
|
|
684
|
-
assert.equal(classifyMidstreamError({ status: 529 }, { ...clean, [exposed]: true }, policy), null);
|
|
685
|
-
}
|
|
686
|
-
assert.equal(MIDSTREAM_RETRY_POLICY.sse.defaultRetries, 3);
|
|
687
|
-
});
|
|
688
|
-
|
|
689
|
-
test('successful SSE parsing cancels and releases the reader', async () => {
|
|
690
|
-
const encoded = new TextEncoder().encode(
|
|
691
|
-
'event: message_start\ndata: {"type":"message_start","message":{"model":"claude","usage":{}}}\n\n'
|
|
692
|
-
+ 'event: message_stop\ndata: {"type":"message_stop"}\n\n',
|
|
693
|
-
);
|
|
694
|
-
let reads = 0;
|
|
695
|
-
let cancels = 0;
|
|
696
|
-
let releases = 0;
|
|
697
|
-
const response = { body: { getReader: () => ({
|
|
698
|
-
read: async () => reads++ === 0 ? { done: false, value: encoded } : { done: true },
|
|
699
|
-
cancel: async () => { cancels++; },
|
|
700
|
-
releaseLock: () => { releases++; },
|
|
701
|
-
}) } };
|
|
702
|
-
const state = {};
|
|
703
|
-
await parseSSEStream(response, null, () => {}, null, null, state);
|
|
704
|
-
assert.equal(state.sawCompleted, true);
|
|
705
|
-
assert.equal(cancels, 1);
|
|
706
|
-
assert.equal(releases, 1);
|
|
707
|
-
});
|
|
708
|
-
|
|
709
|
-
test('pre-aborted SSE still cancels and releases its acquired reader', async () => {
|
|
710
|
-
const controller = new AbortController();
|
|
711
|
-
const reason = new Error('already canceled');
|
|
712
|
-
controller.abort(reason);
|
|
713
|
-
let cancels = 0;
|
|
714
|
-
let releases = 0;
|
|
715
|
-
const response = { body: { getReader: () => ({
|
|
716
|
-
read: async () => ({ done: true }),
|
|
717
|
-
cancel: async () => { cancels += 1; },
|
|
718
|
-
releaseLock: () => { releases += 1; },
|
|
719
|
-
}) } };
|
|
720
|
-
await assert.rejects(
|
|
721
|
-
parseSSEStream(response, controller.signal, () => {}, null, null, {}),
|
|
722
|
-
(err) => err === reason,
|
|
723
|
-
);
|
|
724
|
-
assert.equal(cancels, 1);
|
|
725
|
-
assert.equal(releases, 1);
|
|
726
|
-
});
|