- Added a setup-system-deps action with preloaded-runner guards and apt fallbacks.
- Updated CI workflows to download Linux x64 native artifacts and gate on native job success.
- Renamed coding-agent fast mode to singleton in scripts and test partitioning logic.
- Added settings test-state begin/restore helpers with recursive cleanup in affected tests.
Moved the AI changelog entries flattened by the previous fix run back under their released 15.x headings, leaving the package Unreleased section empty for this PR.
Hardcoding device index `:0` grabbed whatever avfoundation enumerated first
(often a camera or the wrong input), so recording could capture silence or the
wrong source. Both the single-shot and streaming ffmpeg recorder paths now
request the system default input device.
- agent-loop: raise repetition-detection floor to 180 chars and clear thinking
replay anchors when collapsing a detected loop.
- providers/google: ignore empty text parts, retain terminal thoughtSignatures,
and stop function-call signatures clobbering the prior block.
- autolearn: capture goal-mode at the turn boundary; harden managed-skill writes
against hard-links/symlinks (O_NOFOLLOW + nlink); refuse minting managed skills
whose name an authored skill already claims.
- eager tasks: thread agentKind through the session so a custom top-level agentId
still gets always-mode delegation; split Eager Tasks prompt into hard vs soft.
- title-generator: race the online title model against a local tiny-model fallback.
- eager-todo: keep the soft reminder aligned with the todo init schema.
- mcp/stdio: keep close() detaching the read loop instead of awaiting it.
- stream loop: fix collapsing and tool-call thought-signature handling.
GitHub Copilot's Anthropic-compatible proxy (api.githubcopilot.com/v1/messages) validates tool definitions with additionalProperties: false and rejected every tool-bearing turn with `400 tools.0.custom.eager_input_streaming: Extra inputs are not permitted`. The catalog's Anthropic compat builder defaulted supportsEagerToolInputStreaming to true for all hosts, so convertTools added the flag to every tool sent to Copilot.
Gate the flag on host in buildAnthropicCompat (false for github-copilot, matching the existing supportsLongCacheRetention: official pattern), and stop pushing the legacy fine-grained-tool-streaming-2025-05-14 beta header on the Copilot transport — the proxy doesn't whitelist Anthropic beta features either, so the fallback path would 400 too.
Fixes#2558
Direct callers of ./providers/{amazon-bedrock,google-gemini-cli,ollama,openai-codex-responses} bypass register-builtins' iterator-level first-event watchdog. With Bun's native ~300s fetch ceiling disabled in #2422, a stalled upstream that accepts the POST and never sends headers would hang forever.
Combine the caller's signal with AbortSignal.timeout(firstEventTimeoutMs) at each fetchWithRetry site so pre-response stalls fail within the configured budget. Bedrock/Gemini use getStreamFirstEventTimeoutMs (Gemini floors at 5min to match its register-builtins limits); Ollama uses getOpenAIStreamFirstEventTimeoutMs floored by getOpenAIStreamIdleTimeoutMs; Codex SSE threads firstEventTimeoutMs through CodexRequestSetup -> openCodexSseTransport -> openCodexSseEventStream.
Verified with an out-of-tree smoke that aborts a stalling Bedrock fetch via streamFirstEventTimeoutMs: 2_000ms — elapsed 2005ms, stopReason error, errorMessage 'Request was aborted'. Smoke discarded per directive.
Refs #2422
Cumulative reasoning_content dedup keyed on currentBlock.thinking failed
once </think> and visible text arrived: currentBlock flipped to text, the
guard missed, and the next chunk's cumulative snapshot was re-emitted as
a fresh thinking block after the answer had started.
Replaced the block-scoped check with lastCumulativeReasoningBySignature,
a closure-scoped Map keyed by signature (the reasoning field name) that
holds the last full snapshot regardless of currentBlock state. Cumulative
chunks now slice against this tracker, so post-answer snapshots resolve
to an empty delta and never reinstate a phantom thinking block.
Added a regression test covering the post-text cumulative case.
Fixes#2433
Bun's fetch enforces a hard ~300s pre-response timeout that the caller's AbortSignal cannot lengthen. Every streaming provider's first-event/idle/SDK watchdog was silently capped by it, so cold large-context streams (multi-hundred-K prompts against slow-prefill backends) died at exactly 300s with TimeoutError.
- Added FetchWithRetryOptions.timeout (forwarded to fetch) so fetchWithRetry callers can pass Bun's timeout: false / numeric override directly.
- Passed timeout: false in openai-http, openai-codex-responses, amazon-bedrock, google-gemini-cli, ollama where each provider already constructs the fetch init.
- Added timeout to AnthropicFetchOptions and threaded timeout: false through buildAnthropicClientOptions's fetchOptions; anthropic-client already spreads fetchOptions into every fetch call.
- Allowed compat.streamIdleTimeoutMs: 0 in models.yml so the documented per-model disable knob matches the env-var escape hatch.
Verified with an out-of-tree smoke that stalls a local server 305s before responding: pre-fix the streaming provider died at ~300003ms with the Bun TimeoutError; post-fix the request completes (SUCCESS elapsed=305006ms). The smoke is discarded per directive.
Fixes#2422
GitLab Duo OAuth callback handling now rejects HTTPS loopback redirect URIs before browser auth because the embedded callback server only serves plaintext HTTP.
Fixes#2424
Added OpenAI-compatible compat metadata for endpoints that allow tools but reject forced tool_choice. OpenCode Go kimi-k2.7-code now downgrades resolve-gate forcing to auto tool selection while preserving thinking-mode request state.\n\nFixes #2546
- Validated queued toolChoice against active tools in agent and coding-agent sessions.
- Rejected queued forced choices with reason "unavailable" when selected tools were inactive.
- Dropped provider toolChoice payloads when requested function tools were not offered.
- Probed Tokio worker-thread support and fell back to current-thread runtime creation.
- Marked OpenCode Go MiMo catalog entries as not supporting tool_choice so title generation keeps tools available without sending the rejected control field.
- Installed a smaller napi-rs Tokio runtime for pi-natives before async exports can initialize the default multi-worker runtime.
- Added regression coverage for the generated catalog policy, OpenCode Go wire payloads, and native runtime construction.
Fixes#2509
Seed glm-5.2 and glm-5.2[1m] on the zai (GLM Coding Plan) provider
as selectable catalog entries with 1M context, pin the context at
catalog generation so discovery cannot regress to 200k, and use
glm-5.2 for Z.AI API key validation. Default model stays glm-5.1
(bumping requires maintainer sign-off).
- Added a changelog note that abortable wrappers now cancel source streams on timeout abort.
- Added a regression test ensuring OpenAI first-event timeout marks stream cancellation.
- Extended gateway stream control to pass abort signals and onCancel into encodeStream.
- Added optional cancellation control parameters to provider encodeStream handlers.
- Stopped provider stream loops on cancellation and suppressed SSE completion/error output after abort.
- Added a regression test verifying reader.cancel triggers onCancel and aborts upstream request.
- Added a credential-picker `/logout` flow for selecting one OAuth account.
- Added optional `/logout` provider argument and unknown-provider error handling.
- Changed logout handling to delete only the chosen credential and keep others.
- Added AuthStorage APIs to list and remove credentials by id, with remote deletion hook.
- Added shared Google empty-stream retry constants and reused them in CLI.
- Replaced local content checks with hasMeaningfulGoogleContent for retry gating.
- Refactored streamGoogleGenAI to bound empty STOP retries with state reset and reopen.
- Moved stream start emission before retry loops to avoid duplicate start events.
- Applied a finite 64,000-token fallback for OpenAI completion requests when model maxTokens is unavailable.
- Refactored stream max-token computation to use a shared helper and preserve a finite cap when caller and model limits are null.
- Added regression coverage for null maxTokens flows and bumped the model-cache schema to invalidate entries using retired unknown-limit sentinels.
- Replaced unknown model contextWindow/maxTokens sentinels with nullable values across types and catalog data.
- Mapped request token calculations to treat null maxTokens as unlimited output caps.
- Updated remote compaction and context checks to ignore unknown limits by using Infinity/0 fallbacks.
- Adjusted CLI/model registry flows to skip cap enforcement for null limits and render unknown values as '-'.
MiniMax-M3 OpenAI-compatible streams can carry the same reasoning in visible <think> content and structured reasoning_content. The stream parser now treats the structured reasoning field as authoritative for that chunk and collapses cumulative MiniMax reasoning snapshots to deltas.\n\nFixes #2433
- Augmented streaming SSE observers to infer event names from payload fields when the event field is missing.
- Added `X-Stainless-Timeout` request headers derived from `requestTimeoutMs` for OpenAI and Azure response/completions streaming calls.
- Replaced Azure and OpenAI provider calls with `postOpenAIStream` flow.
- Fixed stream error handling by retaining status, headers, and body on failures.
- Fixed stream parsing by handling raw JSON SSE frames and `[DONE]` events.
- Added `OpenAIHttpError` with parsed messages and timeout-aware retry behavior.
- Added remote-compaction tests for timeout, abort, and 500-fallback behavior.
The bundled GitLab Duo `client_id` had its registered redirect URI list
drift away from `http://localhost:8080/callback`, so every browser
authorize request landed on GitLab's "The redirect URI included is not
valid" error page. The random-port fallback in `callback-server.ts` was
guaranteed to break for the same reason (strict OAuth redirect URI
matching).
- Honour `GITLAB_CLIENT_ID` / `GITLAB_REDIRECT_URI` env vars in both the
authorize URL and the token-exchange / refresh POSTs so users running
with their own GitLab OAuth application can replace the bundled
credentials end-to-end.
- When `GITLAB_REDIRECT_URI` is set, advertise the exact string to
GitLab, bind the local callback server to the URI's loopback host/port
(random port for non-loopback URIs that only complete via paste-code),
and pass `OAuthCallbackFlowOptions.redirectUri` so the silent random
port fallback is disabled — a mismatched URI now surfaces the bind
error rather than producing a token GitLab refuses to redeem.
- Surface the new env vars (and the existing `GITLAB_TOKEN` PAT path) in
the browser instructions text when GitLab rejects the URI.
- Thread `ctrl.fetch` through the login flow to match the xiaomi /
anthropic patterns.
Fixes#2424
- Mapped OpenAI completions streams ending with `finish_reason: "error"` to `stopReason: "error"` with the message `Provider returned error finish_reason`.
- Added regression tests for bare `finish_reason: "error"` streams, including tool-call payloads, to verify the error remains retryable.
Concurrent omp --session restores after an unclean shutdown crashed
in SqliteAuthCredentialStore.#initializeSchema() with
SQLITE_BUSY_RECOVERY because the multi-statement schema run installed
PRAGMA busy_timeout=5000 AFTER PRAGMA journal_mode=WAL, the first
lock-taking statement during WAL recovery. Bun's default busy_timeout
is 0, so the lock conflict surfaces immediately.
- packages/ai/src/auth-storage.ts: hoisted PRAGMA busy_timeout to a
standalone first statement, dropped it from the multi-statement
schema run, wrapped SqliteAuthCredentialStore.open() in a 4-attempt
exponential-backoff retry loop on the SQLITE_BUSY family, and the
exhausted-retry error now includes the DB path. Exported
isSqliteBusyError(err) (matches code prefix 'SQLITE_BUSY').
- packages/coding-agent/src/session/agent-storage.ts: same hoist and
the existing retry loop now uses isSqliteBusyError so
SQLITE_BUSY_RECOVERY / _SNAPSHOT / _TIMEOUT also trigger backoff.
- Hoisted busy_timeout before journal_mode=WAL in every other shared
SQLite open path: history-storage, autoresearch/storage,
memories/storage, github-cache, report-tool-issue (auto-QA),
catalog/model-cache; stats/db.ts now sets busy_timeout at all.
- packages/ai/test/auth-storage-sqlite-busy.test.ts pins the contract:
isSqliteBusyError matches every BUSY extended code (rejects
SQLITE_LOCKED, non-errors, strings); open() leaves the connection in
WAL mode (proves busy_timeout ran before journal_mode); open() retries
through synthetic SQLITE_BUSY_RECOVERY; non-BUSY errors (SQLITE_CORRUPT)
short-circuit; exhausted retries throw an error mentioning the DB path
with exactly 3 sleeps for a 4-attempt budget.
Fixes#2421