- Improved the leaked-thinking stream projector to clone and sync native tool-call blocks directly.
- Eliminated the need for placeholder IDs and complex rekeying logic in the event controller and argument reveal module.
- Simplified native tool-call validation in owned-stream processing by requiring only a non-empty name.
- Added comprehensive unit tests to ensure tool-call IDs and partial JSON parameters remain intact during healing.
Added a cross-turn tool-call loop guard that hashes canonical tool names and arguments, ignores intent metadata, and injects a hidden redirect when identical calls reach the configured threshold.
Fixes#3971
Cursor and Devin called parseStreamingJson(accumulated) on every
tool-call argument delta, re-parsing the entire accumulated buffer each
chunk. For a payload of N bytes arriving as M small deltas this gave
~M full parses of an ever-growing buffer — O(N^2) total work — so
sizeable write/apply_patch arg blobs stalled streaming with visible
CPU cost and delayed toolcall_delta events.
Both providers now mirror the Anthropic / Bedrock / OpenAI pattern:
parseStreamingJsonThrottled with kStreamingLastParseLen persisted per
tool-call block, bounding mid-stream parse work to O(N). The
authoritative end-of-stream parse is unchanged (Devin's toolcall_end
loop; Cursor's toolCallCompleted mcp branch now runs a full
parseStreamingJson of the accumulated buffer before merging with the
completion frame, so the throttled tail is finalized correctly).
Fixes#3946
- Updated tests to verify tool_call_id generation when assistant and tool IDs normalize to empty strings.
- Added test assertions to ensure raw non-empty malformed IDs containing pipes do not leak through and instead use generated fallbacks prefixed with call_.
- Added `hasUsableNativeToolCall` helper to verify that a streaming tool call has non-empty, trimmed name and id values.
- Retain projection initialization and updates on subsequent deltas if the provider emits native tool identifiers late.
- Guard tool call synchronization and late salvage logic to prevent empty or invalid placeholders from corrupting streaming state.
Widened local OpenAI-compatible stream watchdog defaults so llama.cpp and loopback providers can cold-load models without hitting the first-event abort.
Added regression coverage for both chat-completions and Responses compat.
Fixes#3940
- Streamed parameter chunk deltas as `toolArgDelta` events during execution for Anthropic and DeepSeek dialects.
- Integrated streaming tool call tracking in `InbandStreamProjector` to preserve native tool identifiers and partial JSON arguments.
- Added message validation functions to detect and drop malformed tool calls with empty or whitespace-only IDs.
- Introduced comprehensive suite of integration and unit tests validating argument delta streaming and tool-call sanitization.
- Introduced the `FencedThinkingScanner` class to track nested Markdown code fences inside thinking blocks.
- Integrated the new scanner into Gemini and generic inband thinking parsers to prevent premature termination of thinking blocks.
- Added comprehensive unit and integration tests to validate nested fence retention and inline close resolution during stream parsing.
- Raised `GEMINI_HEADER_RUNAWAY_THRESHOLD` from 10 to 24 to avoid false-positive interrupts on legitimate, complex reasoning blocks.
- Added a regression test verifying that 10 distinct, progressing headers do not trip the detector while 24 headers still trigger it.
- Added LLAMA_CPP_TOOL_CALL_PARSE_PATTERN and cleared Flag.Transient inside classifyMessage so the agent-level auto-retry no longer replays the same prompt against the same broken model state.
- Consolidated the duplicated detection pattern in the Ollama provider and the user-facing error rewrite to reuse the shared flags constant.
- Added a regression assertion that the finalized errorId is not Transient and not AIError.retriable for the malformed tool-call response.
Esc and wizard abort signals now race the MCP OAuth login promise directly, so cancellation wins even before OAuthCallbackFlow reaches its callback wait and registers an abort listener. OAuthCallbackFlow also checks pre-aborted signals before opening/waiting on the callback server and its wait path handles already-aborted signals.
Threaded the abort signal into MCP OAuth fetches so dynamic client registration, metadata discovery, authorization probes, and token exchange unblock promptly when the user cancels.
Added a regression test where MCPOAuthFlow.login never observes ctrl.signal, matching the pre-wait race called out in review.
Fixes#3888
PR review pointed out that the unconditional opt-out blocks the safe case: when no static `client_id` is set, `MCPOAuthFlow.#tryRegisterClient` does DCR with whichever loopback URI we actually bound, so the provider issues a `client_id` tied to the fallback port and the authorize request is accepted. First-install users whose default port 3000 is busy could no longer authenticate.
Gate `allowPortFallback` on `staticClientIdFromConfig(config) === undefined`: pinned client ids (config-supplied or embedded in the authorization URL) keep the strict-port behavior that fixes#3887; unresolved client ids fall back as before. Factored the static client-id resolution into a module-level helper so `MCPOAuthFlow.#resolveClientId` and `resolveCallbackOptions` share the same logic.
Added an oauth-flow.test.ts case that wires a mock registration endpoint + occupies the preferred port and asserts the fallback URI is what DCR registers and what the authorize request advertises. Tightened the existing strict-port test's title to call out the static-clientId trigger.
When the MCP OAuth callback server's preferred port (default 3000) was unavailable, `OAuthCallbackFlow.#startCallbackServer` silently bound a random port and forwarded the mismatched `redirect_uri` to the authorization server. Providers that validate redirect URIs against a registered callback (e.g. Atlassian) returned an opaque HTTP 500, leaving the local flow waiting for a callback that never arrived until the 5-minute timeout fired.
Added `OAuthCallbackFlowOptions.allowPortFallback` (default `true`, preserving every existing AI-provider flow) and threaded `allowPortFallback: false` through `MCPOAuthFlow`'s `resolveCallbackOptions`. With fallback disabled, login now throws a `ConfigurationError` that names the busy port and the remediation (free the port, or set `oauth.callbackPort`/`oauth.redirectUri` in `mcp.json`) before opening the browser. The existing `oauth.redirectUri`-strict path is reworded along the same lines so callers see one consistent message family.
Fixes#3887
- Guarded the `all_turns` reasoning context value to OpenAI models version 5.4 or greater.
- Suppressed `reasoning.context` defaults and explicit overrides when `all_turns` is requested on unsupported models to prevent server rejection.
- Introduced `supportsAllTurnsReasoningContext` helper in `@oh-my-pi/pi-catalog/identity` using semver classification.
- Updated request-transformer and response-options logic to conditionalize the request payload shaping.
- Expanded test coverage to verify correct fallback behavior and explicit overrides across gpt-5.x versions.
- Added `leaked-thinking-stream.ts` whose `wrapLeakedThinkingStream`/`LeakedThinkingProjector` re-projects any provider stream, splitting leaked ```thinking`/`<think>` fences out of the visible-text channel into structured thinking blocks live while preserving text/thinking/tool signatures.
- Wrapped the shared `withProviderInFlightLimit` dispatch (both the unlimited and queued paths) and the standalone GitLab Duo path in `stream.ts` so healing covers every provider exit idempotently.
- Reworked `google-gemini-cli.ts` visible-text emission onto `StreamMarkupHealing` with `feedVisibleText`/`flushVisibleText` and explicit block bookkeeping, lifting leaked Gemini fences ahead of native tool calls.
- Added `leaked-thinking-stream` coverage and a gemini-cli healing case, and updated `stream-auth-retry`/`google-gemini-cli-alignment` expectations for the healed event sequence and dropped empty-text residue.
- Recorded the changelog `### Fixed` entry (whose run also carries the adjacent Codex `all_turns` line).
- Added `google-interactions.ts` streaming the Gemini Interactions API (model-mode steps, thought/tool/text deltas, usage, `previous_interaction_id` lineage) shared by the direct Google and Vertex providers.
- Routed `streamGoogle` and `streamGoogleVertex` through `resolveInteractionDispatch`, defaulting Gemini 3+ onto Interactions (official endpoint for direct Google, bearer/ADC for Vertex) with transparent `:streamGenerateContent` fallback and a `useInteractionsApi: false` opt-out.
- Added explicit Vertex bearer support in `google-auth.ts` via `GOOGLE_CLOUD_ACCESS_TOKEN`/`CLOUDSDK_AUTH_ACCESS_TOKEN` plus a `hasVertexBearerCredentialsHint` probe gating the auto-default.
- Added `useInteractionsApi`/`storeInteraction`/`previousInteractionId` to `StreamOptions` and `GoogleSharedStreamOptions`, threading them through `mapOptionsForApi`.
- Added `google-interactions` coverage and pinned the existing generateContent assertions with `useInteractionsApi: false`.
- Reordered `AuthStorage.getApiKey` and its async session variant to resolve stored OAuth, then an env var, then a stored static api_key — so an explicit `GEMINI_API_KEY`-style env var now wins over a stale broker-migrated key.
- Reworked `getCredentialOrigin` and `getApiKeySource` to mirror that precedence via a `describeStored` helper, reporting `oauth`/`env`/`api_key` in the new order.
- Updated `auth-storage-api-key-login` to neutralize ambient env keys with a `getEnvApiKey` spy, and `auth-storage-credential-origin` to assert OAuth outranks api_key and env outranks a stored api_key.
Mapped Cerebras gemma-4-31b dynamic discovery to include image input capability and covered the OpenAI Chat Completions image_url serialization path.
Fixes#3854
- Introduce `fallbackUrl` to allow retrying requests on the global Vertex endpoint when a 404 is encountered on a regional endpoint.
- Correctly track tool call names to facilitate accurate function response mapping.
- Update `serviceTier` type definitions to accurately reflect allowed values.
- Refine vertex endpoint location resolution to ensure better compatibility with ambient regional settings.
Reviewer flagged that the omit/forced-tool gate matched every public id, regressing Fireworks and OpenRouter Kimi K2.7 Code (non-zai dialects).
Added Fireworks + OpenRouter regression coverage.
- Migrated global service tier settings to a per-model-family architecture (OpenAI, Anthropic, Google).
- Implemented `ServiceTierByFamily` mapping to allow independent configuration and resolution per provider.
- Added automatic migration logic for legacy service tier and fast-mode application settings.
- Updated telemetry, session management, and task execution to support provider-specific tier resolution.
Kimi K2.7 Code rejects disabled thinking on native Kimi endpoints, so route caller disable requests through the omit mode and let the model default to required thinking.
Added regression coverage for the title-generator-style Kimi Code request, Moonshot K2.7 Code variants, and K2.6's still-supported disabled-thinking path.
Fixes#3852