The command row now reflects an executing state while a command runs,
instead of appearing pending until it finishes. The message translator
includes the command-output marker on the running command row so the
webview renders it as executing; the row is finalized with output and a
completed flag when the command ends.
Also remove the unused onChange parameter from the foreground run_commands
path: the SDK runtime does not pass it, so it had no effect. Foreground
command output is surfaced to the chat at completion, not incrementally.
* fix(vscode): wire auto compact into SDK sessions
* test(sdk): cover both directions of useAutoCondense task override
The previous test left the global mock at `true` for both calls, so the
`taskSettings: true` branch would have passed even if task settings were
ignored entirely. Make the mock read a mutable flag and flip it to `false`
before the second call so both override directions — task `false` over
global `true`, and task `true` over global `false` — are genuinely
exercised.
* Fix mock return type in cline-session-factory test
The getGlobalSettingsKey mock inferred a literal 'false | undefined' return type, so later mockImplementation overrides returning 'true' failed type checking (TS2345). Annotate the implementation as 'boolean | undefined' to widen the inferred mock signature.
---------
Co-authored-by: Mikołaj Kondratek <19799111+mkondratek@users.noreply.github.com>
The MCP SDK calls redirectToAuthorization() on every connection attempt,
and a single server can be reconnected repeatedly (settings watcher,
reconnect handler, restart). Regenerating the OAuth `state` on each call
replaced the state stored for a flow whose authorization URL the user may
already have open, so the completed callback failed validation with
"Invalid OAuth state".
redirectToAuthorization() now keeps an in-progress, still-fresh flow
instead of starting a new one (freshness measured from when the flow
started, never extended, so a stale flow always expires). The PKCE
verifier is pinned to the kept flow so token exchange still validates.
Also add local dev/test tooling:
- src/dev/mcp-oauth-test-server: a zero-dependency OAuth AS + MCP
StreamableHTTP server for exercising the flow locally, with
fault-injection flags (--auto-deny, --slow-authorize, --code-ttl).
- src/extension.ts: a debug-only globalThis.__clineHandleUri hook (gated
on CLINE_CAPTURE_BROWSER) so the debug harness can deliver simulated
vscode:// OAuth callbacks; documented in the harness README.
Bedrock requests built by the SDK adapter dropped the AWS region and
authentication mode, so a pasted Bedrock API key (awsBedrockApiKey +
awsAuthentication "apikey") was silently ignored and requests fell
through to the SigV4 credential chain with no region.
Two bugs, both verified end-to-end against a live Bedrock endpoint via
the debug harness:
1. The SDK ProviderConfig was built with only providerId/modelId/apiKey/
baseUrl. New bedrock-config.ts maps the legacy ApiConfiguration onto
the SDK's structured region + aws block (including the webview's
"credentials" radio -> SDK "iam"), wired into both inference paths
(buildSdkProviderConfig for utility calls, buildSessionConfig for the
main task loop).
2. The main chat path's gateway config is built by core from the
providers.json `stored` entry, which the session's providerConfig does
not override. A stale Bedrock entry (e.g. a legacy migration with
region us-east-1 + SigV4 keys) silently won, sending requests to the
wrong region (403). buildSessionConfig now persists the
StateManager-derived Bedrock settings to providers.json so `stored` is
authoritative. The bearer apiKey is only persisted for api-key auth to
keep stored clean for profile/iam.
Also documents the ELECTRON_RUN_AS_NODE debug-harness gotcha in
.clinerules/general.md.
The nightly 'vscode test' job (npm run ci:build -> build:webview -> tsc -b) failed on both Linux and windows-latest:
ZAiProvider.tsx: Argument of type 'Event | FormEvent<HTMLElement>' is not assignable to parameter of type 'Event'
VSCodeDropdown's onChange supplies 'Event | React.FormEvent<HTMLElement>', but the getEventValue helper (added when the zai provider was moved to providers.json) was typed to accept only Event. The helper only reads target.value, which exists on both, so widen the parameter to the same union the dropdown provides.
Note: this only surfaces under 'tsc -b' / 'tsc --noEmit -p tsconfig.app.json'; the webview's root tsconfig.json is a solution file with files:[] so a bare 'tsc --noEmit' checks nothing.
The separate 'test / test' job failure is an aggregate gate that fails because vscode-test failed; fixing this resolves it too.
The nightly CI 'Unit Tests with coverage - Linux' step (npm run test:unit) failed in ts-node compilation:
state-keys.ts: Module 'vscode' has no exported member 'LanguageModelChatSelector'
This surfaced as a misleading 'Cannot find package @shared/...' ERR_MODULE_NOT_FOUND: Mocha tried require() first (which threw the ts-node TSError), then fell back to import(), whose ESM resolver cannot resolve the @shared/* path alias.
tsconfig.unit-test.json is driven by ts-node, which defaults to files:false and compiles modules on demand from their imports. The loose ambient augmentation in src/types/vscode-language-model.d.ts was therefore never loaded, so state-keys.ts failed to compile.
Set ts-node.files=true and add src/types/**/*.d.ts to include so the augmentation is part of the unit-test program. Mirrors the earlier tsconfig.test.json fix for the separate build toolchain.
The nightly CI "Build Tests and Extension" step (npm run ci:build) failed
in compile-tests (tsc -p tsconfig.test.json) with:
getVsCodeLmModels.ts: Property 'lm' does not exist on type 'typeof import("vscode")'
state-keys.ts: Module '"vscode"' has no exported member 'LanguageModelChatSelector'
@types/vscode is pinned to 1.84.0, which predates the Language Model API.
The repo compensates with an ambient augmentation in
src/types/vscode-language-model.d.ts, which the main tsconfig picks up via
its 'src/**/*' include. tsconfig.test.json overrides include to only
'src/**/*.test.ts'; listing src/types under typeRoots does not load a loose
.d.ts (typeRoots only auto-loads @types-style package folders), so the
augmentation was missing from the test program.
Add 'src/types/**/*.d.ts' to tsconfig.test.json include so the augmentation
is part of the test compilation.
The completion signal (attemptCompletionSeen) was scoped to the message
translator's per-iteration reset(), so a turn that called the completion tool
and then ran another iteration before 'done' lost the signal — the turn ended
as awaiting_followup instead of completed and the footer showed no
'Start New Task' button despite the green Task Completed box.
Make the completion signal turn-scoped: reset() only clears streaming pointers;
a new clearTurnOutcome() clears it at genuine turn/task boundaries (initTask,
reinitExistingTaskFromId, askResponse). Also recognize the SDK's built-in
submit_and_exit completion tool (summary field) alongside the VSCode
attempt_completion tool (result field).
Removes the webview-message-state design/findings docs and rewrites the
related comments to describe the system as-is.
Webview now gates turnState by seq (a stale snapshot can no longer revert
streaming->idle), never empties a live transcript on a lone newer-epoch partial,
and routes a follow-up after completed/awaiting_followup to askResponse instead
of starting a new task.
Backend phase emission fixes surfaced by live testing:
- post state on turn end even when the done event carries no messages
- askResponse sets phase=streaming (resume/continue shows Cancel, unblocks send)
- a post-cancel turn-complete straggler no longer clobbers resumable
Adds reducer, send-routing, and session-event-coordinator unit tests. See
src/sdk/docs/webview-message-state-design.md §11 for the full debugging log.
S7 (a + c) of the message-state redesign — removes two order-dependent hacks now that
TurnState is authoritative.
(a) ask_question / ask_followup_question are suppressed from the generic say:"tool"
renderer (both content_start and content_end). The SdkInteractionCoordinator services
these and emits the proper ask:"followup"; the generic tool row was an orphan partial
that never finalized and defeated the tail heuristics. The CLI already does this.
(c) The `done` handler no longer synthesizes a trailing ask:"completion_result" — that
was the "must be last message" hack (ENG-1887) that existed only because the webview
inferred UI mode from the array tail. `done` now emits no transcript message and only
signals turnComplete; the webview reads phase from TurnState (completed when
attempt_completion was used, else awaiting_followup). The green "Task Completed" box
still comes from the say:"completion_result" emitted at attempt_completion content_end.
Translator unit tests updated to the new contract (done → 0 messages, turnComplete=true).
Deferred to a follow-up (lower risk if left): (b) collapsing the approval ask onto the
streaming tool's id (needs cross-coordinator id threading), and (d) the mistake_limit
forced abort (now harmless since phase is authoritative). The persisted-history renderer
still appends its own trailing ask so reopened tasks show the resume affordance —
intentional.
428 SDK unit tests pass; tsc + biome clean.
S6 of the message-state redesign. sdkHost.abort() is cooperative — the SDK can emit a
few more events after it. Previously cancelTask aborted first and only the (post-abort)
!isRunning filter stripped two trailing ask types, letting say:* stragglers land after
the resume_task ask and wedge the UI.
Now cancel raises the fence SYNCHRONOUSLY before the abort:
- SdkController.cancelTask sets turnState.phase = "resumable" (already in S4), then
- SdkTaskControlCoordinator.cancelTask calls raiseCancelFence() (epoch bump) BEFORE
awaiting sdkHost.abort().
Any event the SDK emits after the abort request therefore carries the OLD epoch and is
dropped by the webview's convergent reducer; the authoritative phase is "resumable"
(Resume Task), independent of the message tail. Order matters and is covered by a unit
test (fence before abort).
Usage accounting is exempt from the fence — it was never gated by the message filter, so
a post-cancel usage event still bills the tokens the provider actually generated.
The legacy !isRunning ask-only filter is now redundant but kept as defense-in-depth.
428 SDK unit tests pass; tsc + biome clean.
S5 of the message-state redesign. The webview decided "thinking vs approving vs done"
and which buttons to show by inspecting the TAIL of clineMessages. Because the backend
appends bookkeeping (api_req_started usage) after content and even after approval asks,
the tail routinely meant the wrong thing — producing stuck "Thinking", vanishing
Approve/Reject, and the footer disagreeing with the buttons (RC1, reproduced on camera).
Now the webview reads the authoritative backend-owned TurnState (added in S4):
- buttonConfig: add buttonsForPhase(turnState, anchoredMessage) and the dispatcher
getButtonConfigFromState(messages, turnState, mode). The button SET is chosen by phase;
approval labels (Approve vs Save, Run Command, MCP, subagents) come from the anchored
message (turnState.anchorTs). When turnState is absent (classic/older state) it falls
back to the legacy tail-walking getButtonConfigForMessages.
- ActionButtons reads turnState from useExtensionState and uses getButtonConfigFromState.
- MessagesArea.isWaitingForResponse short-circuits to `phase === "streaming"` when
turnState is present (and only shows the footer loader until a content row is actually
streaming); the legacy tail inference is kept as the fallback.
Button actions (approve/reject/proceed/new_task) already send a fixed responseType
independent of clineAsk, and the SDK backend resolves the pending approval/followup
promise — so routing is correct under TurnState. Classic fallback paths are untouched.
New unit tests cover buttonsForPhase (every phase + anchored-label selection +
mistake_limit-vs-api_req_failed) and getButtonConfigFromState (prefers TurnState over a
trailing bookkeeping tail; legacy fallback). 68 webview chat tests pass; tsc + biome clean.
S4 of the message-state redesign. Introduces the single source of truth for the
webview's UI mode so it no longer has to be inferred from the tail of clineMessages
(the root of RC1: missing/stuck "Thinking", vanishing approval buttons, footer and
buttons disagreeing).
- Add TurnPhase / TurnState to shared types and ExtensionState.turnState (rides inside
state_json; no proto change).
- Add TurnStateTracker, owned by SdkController, sharing the one id/seq/epoch authority.
Each transition stamps a fresh seq so the webview keeps only the newest TurnState.
- Set the phase at the exact lifecycle points where the backend knows it:
streaming — initTask / reinit / user responded (resolvePending*)
awaiting_approval — handleRequestToolApproval (anchored on the ask)
awaiting_followup — handleAskQuestion; and a turn that ends WITHOUT attempt_completion
completed — turn ends and attempt_completion was used
error — onSendError; mistake_limit
resumable — cancelTask (set before abort)
idle — clearTask
The completed-vs-awaiting_followup decision uses
MessageTranslatorState.wasAttemptCompletionSeen().
- getStateToPostToWebview now ships turnState in every snapshot.
This is ADDITIVE: the webview does not read turnState yet (S5 wires footer/buttons to
it and deletes the tail heuristics). Classic/legacy paths leave turnState undefined and
keep the legacy behavior. 427 SDK unit tests pass (incl. new turn-state-tracker tests);
tsc and biome clean.
S3 of the message-state redesign. The webview received the same conversation over
two unordered, fire-and-forget channels — incremental partial messages and full
state snapshots — and the state handler REPLACED clineMessages wholesale ("// HACK:
Preserve clineMessages if currentTaskItem is the same"). A stale snapshot captured
before the last message landed could clobber the transcript, dropping the final
message and leaving the UI stuck on "Thinking…" (reproduced earlier on camera).
Introduce a pure convergent-replica reducer (messageReducer.ts) keyed on the three
extension-stamped quantities from S2:
- ts : identity / merge key
- seq : freshness (higher seq wins for the same ts)
- epoch : conversation/replica fence (newer replaces, older is dropped, equal merges)
applyMessage / applyStateSnapshot are total and side-effect free:
- older epoch -> drop (straggler from a previous task/render)
- newer epoch -> replace the transcript wholesale (new task / history load)
- same epoch -> merge by ts keeping the higher seq; a snapshot may ADD/UPDATE
rows but NEVER truncate, so it can't drop a message the partial
stream already delivered. Stale (older stateVersion) snapshots are
ignored wholesale.
ExtensionStateContext now feeds both subscription callbacks through the reducer via a
replicaRef, replacing the wholesale-replace HACK and the findLastIndex append. Classic/
legacy state is unstamped (epoch 0 / version 0) and merges by ts exactly as before.
Tests (messageReducer.test.ts): deterministic cases mapping 1:1 to the bugs (stale
snapshot must not shrink the transcript; lower-seq ignored; partial->final in place;
older-epoch straggler dropped; older-version snapshot ignored), PLUS an
order-independence proof — all 120 permutations of a causal log, with duplication and
with non-final drops, converge to the same canonical state. This is the high-confidence
guarantee that the webview cannot get stuck regardless of delivery timing.
62 webview chat tests pass; tsc and biome clean.
S2 of the message-state redesign. Lays the groundwork for the convergent-replica
webview reducer (S3) so the webview can never get stuck on stale/out-of-order
delivery.
Stamping (extension-owned, synchronous, from the single MessageIdMinter):
- Every ClineMessage flowing to the webview is stamped in SdkMessageCoordinator with
a fresh `seq` (freshness) and the current `epoch` (conversation/replica fence),
before it is stored or emitted. The same object references go to both the message
state handler and the partial-message stream, so both channels carry identical
stamps. An updated message (partial -> final, same ts) passes through again and
gets a higher seq, so the freshest copy always wins regardless of arrival order.
- Every state snapshot is stamped in SdkController.getStateToPostToWebview with a
fresh `stateVersion` (sampled from the same counter) and the current `epoch`.
- `epoch` is bumped at every conversation boundary via a new
resetMessageTranslatorAndFence() wired into the existing resetMessageTranslator
sites (task start/clear, history open, reinit, mode rebuild, new-session
follow-up). iteration_start streaming resets do NOT bump it.
Transport:
- ClineMessage proto gains seq/epoch (fields 24/25) and the conversions carry them.
- ExtensionState gains stateVersion/epoch (ride inside state_json, no proto change).
Fire-and-forget delivery:
- sendPartialMessageEvent and sendStateUpdate no longer await postMessage to the
webview. A hidden/reloaded/closed webview can make postMessage hang or resolve
false; awaiting it could stall the backend turn loop. Correctness no longer
depends on any single delivery — the webview will be a convergent replica (S3).
All new fields are optional/default-0 so the classic/legacy path is unaffected.
Cancel's epoch bump + fence-before-abort and the remaining one-off Date.now() mint
sites are handled in later steps (S6/S2-tail). 423 unit tests pass; tsc clean.
Message ids (ClineMessage.ts) were minted from Date.now() in two independent
generators: the live message translator (pure ++counter seeded once) and the
interaction coordinator (Math.max(Date.now(), last+1)). Because the translator
counter drifts behind wall-clock, it can later catch up to a clock-based id
minted by the interaction coordinator, producing colliding ids for different
messages. That breaks any merge-by-id scheme on the webview side.
Introduce a single process-wide MessageIdMinter (pure monotonic id/seq/epoch
counters, never reads the clock) owned by MessageTranslatorState and shared by:
- live SDK event translation,
- the interaction coordinator (tool approval / ask_question / user_feedback),
- history rendering (sdkMessagesToClineMessages).
This makes every id globally unique within the process, so regenerated history
ids never overlap live-session ids. Behavior is otherwise unchanged.
Also adds the message-state pipeline design + investigation docs under
src/sdk/docs.
S1 of the message-state redesign; seq/epoch stamping and the remaining one-off
Date.now() mint sites follow in S2.
After inspecting the SDK's generic models-URL fetcher
(sdk/packages/core/src/services/providers/model-source.ts
`fetchModelIdsFromSource` + `resolveModelsSourceUrl`), update the TODOs on the
bespoke refresh*Models handlers to capture the real constraint discovered:
- The SDK fetcher exists and is provider-agnostic, but returns model *ids only*
(unknown ids get placeholder ModelInfo with no real pricing/capabilities).
- `mergeKnownModels` treats a registered `modelsSourceUrl` as the authoritative
"installed" list (Ollama/LM Studio semantics) and DISCARDS the curated catalog
when the live fetch returns results.
So simply registering `modelsSourceUrl` for Groq/Baseten/Hicap/HuggingFace/
Vercel/OpenRouter would regress rich model metadata. Proper consolidation needs
an SDK enhancement first (merge-mode or richer per-provider parsing), then the
extension handlers + RPCs can be deleted. refreshGroqModels.ts carries the
detailed note; the others reference it.
refreshClineRecommendedModels now delegates the HTTP fetch + response
normalization + offline fallback to the SDK's fetchClineRecommendedModels
(@cline/core), removing ~80 lines of duplicated logic. The extension wrapper
keeps its distinct behavior: the CLINE_RECOMMENDED_MODELS_UPSTREAM feature-flag
gate, the in-memory TTL cache, and in-flight dedup. The proxy-aware fetch
(@/shared/net) and the configured apiBaseUrl are passed through to preserve
network/proxy behavior. The SDK's offline fallback list is identical to
CLINE_RECOMMENDED_MODELS_FALLBACK, so offline behavior is unchanged.
Because the module now imports the ESM-only @cline/core, its unit test moves
from mocha to vitest (joining the other SDK-touching models tests): added to
vitest include + mocha ignore, and fetchClineRecommendedModels added to the
vitest @cline/core stub (and the mocha/integration @cline/core mocks for
completeness). Rewrote the test vitest-native, asserting flag-gate, delegation,
and flag re-check.
Also:
- Add TODO(sdk-consolidation) notes to the remaining bespoke live-model-refresh
handlers (Groq, Baseten, Hicap, HuggingFace, Vercel AI Gateway, OpenRouter)
documenting the path to share them with the CLI via the SDK (register
modelsSourceUrl) and then delete the extension-only handlers + RPCs. These
are NOT migrated yet because the SDK does not currently live-fetch those
providers (only ollama/lmstudio register modelsSourceUrl), so deleting them
today would regress to the curated catalog.
- Remove an unused local type (RuleLoadPart) found while reviewing biome
noUnusedVariables output.
tsc --noEmit clean; vitest 439 passing.
- services/telemetry/events/EventHandlerBase.ts: abstract base for telemetry
event handlers whose concrete subclasses were removed with the classic task
code; no remaining references (it was the only file left in events/).
- services/auth/AuthServiceMock.ts: test mock with no importers after the
associated test was deleted.
Note: the rest of services/* (TelemetryService, McpHub, FeatureFlagsService,
ErrorService and their IFoo interfaces) IS live — the interfaces only looked
unreachable to the esbuild oracle because they're consumed via type-only
imports (erased at emit). Verified via importer cross-check; tsc + vitest green.
- Delete src/utils/cost.ts (+ test): calculateApiCostAnthropic/OpenAI/Qwen had
no consumers left after the provider handlers were removed (only the test
referenced them).
- Remove createOpenAIClient from src/shared/net.ts (no remaining callers) and
its now-unused openai + EnvUtils imports. The proxy-aware fetch and
getAxiosSettings exports remain.
tsc --noEmit clean, vitest 436 passing.
Remove dependencies that became unused after the legacy API provider handlers
and tree-sitter service were deleted (no remaining imports in src, webview, or
build config):
@anthropic-ai/vertex-sdk, @aws-sdk/client-bedrock-runtime,
@aws-sdk/credential-providers, @azure/identity,
@cerebras/cerebras_cloud_sdk, @google-cloud/vertexai, @mistralai/mistralai,
@sap-ai-sdk/ai-api, @sap-ai-sdk/orchestration, @sap-cloud-sdk/connectivity,
ollama, tree-sitter-wasms, web-tree-sitter
Also remove the now-dead copyWasmFiles esbuild plugin (it only copied
tree-sitter WASM files for the deleted code-definition service). Kept openai,
@anthropic-ai/sdk, @google/genai, and aws4fetch — still imported by live code.
Verified: extension + standalone esbuild builds succeed, tsc --noEmit clean,
vitest 436 passing.
The previous deletion commit (2c9bd62) removed
core/assistant-message/parse-assistant-message.ts and
core/permissions/CommandPermissionController.ts, but the corresponding barrel
edits (removing their re-exports from index.ts) were dropped by lint-staged's
stash and never committed, leaving HEAD referencing deleted modules. Remove the
dead re-exports so the barrels only export live members.
Computes src files unreachable from the shipped entry points (extension host + standalone host used by JetBrains/CLI) plus webview shared refs, using esbuild metafile reachability. Used to drive the post-SDK-migration dead-code deletions; keep for future pruning. Note: results still need a tsc-gated importer cross-check because esbuild drops import-type-only edges.
Removes additional source files unreachable from any shipped entry point
(extension host, standalone host, webview) after the SDK migration, verified
via esbuild reachability + a per-file importer cross-check that excludes any
file still referenced by a live non-test survivor or generated/test glue, then
gated on tsc --noEmit + vitest (436 passing):
- services/ripgrep, integrations/notifications, utils/string, utils/tabFiltering
- core/assistant-message/parse-assistant-message
- core/hooks: hook-model-context, notification-hook, precompact-executor,
PreToolUseHookCancellationError
- core/permissions/CommandPermissionController
- core/workspace/detection
- integrations/claude-code: run, message-filter
- integrations/editor: FileEditProvider, detect-omission
- integrations/misc: extract-file-content, extract-images
Dropped the now-dead re-exports from core/assistant-message/index.ts and
core/permissions/index.ts (those barrels stay; they still export live types
used by generated host glue).
src/services/tree-sitter/** (language parsers + queries for the classic
list_code_definition_names path) is unreachable from any shipped entry point
after the SDK migration. Verified via scripts/find-dead-src.mjs (esbuild
reachability from extension + standalone entries, plus webview shared refs) and
an importer cross-check (no live value or type importers), gated on
tsc --noEmit + vitest (436 passing).
The classic system-prompt builder (core/prompts/system-prompt/**), the
deep-planning prompt variants, core/prompts/commands.ts, and the
core/slash-commands handler are no longer reachable from any shipped entry
point (extension host, standalone host, or webview) after the SDK migration:
the SDK provides prompt construction (buildClineSystemPrompt) and slash-command
handling. Removed them and their orphaned tests.
Kept core/prompts/responses.ts (still live) and its tests.
Dead-code reachability was computed with scripts/find-dead-src.mjs (esbuild
metafile reachability from src/extension.ts + src/standalone/cline-core.ts,
plus webview src/shared references), then gated on tsc --noEmit + vitest.
Now that buildApiHandler routes through the @cline/llms SDK, the legacy
per-provider handler classes and their supporting code are unused. Remove them:
- apps/vscode/src/core/api/providers/** (all 40+ handler classes, types, tests)
- apps/vscode/src/core/api/transform/** except stream.ts (format/stream/
tool-call helpers only the handlers used)
- apps/vscode/src/core/api/utils/** (messages/responses API support)
- apps/vscode/src/core/api/retry.ts (+ test)
- apps/vscode/src/shared/sdk-handler-models.ts (getProviderModelFromSdk;
only the deleted handlers' getModel() used it)
core/api now contains just index.ts (types + SDK re-exports), transform/stream.ts
(ApiStream types still referenced by the local ApiHandler interface), and
adapters/.
Supporting changes to keep everything compiling/working:
- context-window-utils: drop the dead `api instanceof OpenAiHandler` DeepSeek
branch (handlers are SDK GatewayApiHandlers now); the 64k switch case already
handles DeepSeek context sizing.
- Relocate fetchLiteLlmModelsInfo from the deleted litellm handler into
core/controller/models/fetchLiteLlmModels.ts (used by refreshLiteLlmModels).
- Move the `declare module "vscode"` Language Model API augmentation (previously
carried by the vscode-lm handler) into src/types/vscode-language-model.d.ts so
live consumers (getVsCodeLmModels, vsCodeSelectorUtils) keep their types.
- Move the @google/genai test mock out of the deleted providers dir to
src/test/fixtures/google-genai-mock.ts and repoint test-setup.js.
Replace the legacy per-provider buildApiHandler factory with an SDK-backed
handler (apps/vscode/src/sdk/sdk-api-handler.ts) built via @cline/llms
createHandler(). The two standalone callers (commit-message generation and
explain-changes) now import buildApiHandler directly from the SDK module;
@core/api stays types-only (re-exporting SDK types) so it can keep being
imported widely without pulling the SDK runtime graph into activation.
Also:
- Delete the now-dead OpenRouterHandler and its test (createOpenRouterStream
stays; it is still used by ClineHandler).
- buildSdkProviderConfig never sends both reasoning.effort and
reasoning.max_tokens (some providers reject it), and supports
disableReasoning for fast one-shot utility calls; commit-message and
explain-changes opt in.
- commit-message generation surfaces the real SDK stream error instead of a
generic "empty API response".
- getGitDiff: run git diff --staged even before the first commit; only gate
the git diff HEAD fallback on having commits (fixes "no changes" for the
initial commit of a new repo).
- Delete refreshClineModels.ts, refreshClineModelsRpc.ts, and test
- Remove refreshClineModelsRpc proto RPC
- Remove clineModels state from ExtensionStateContext, StateManager cache, disk
- ClineModelPicker and useOnboardingModels use SDK catalog directly
- Replace ThinkingBudgetSlider with ReasoningEffortSelector for Cline provider
- Use supportsReasoning from SDK catalog instead of hardcoded model names
- Add ProviderReasoningPatch to proto and ProviderConfigPatch contract
- Wire reasoning effort changes through writeProviderConfig to SDK ProviderSettingsManager
- Remove hardcoded Claude switch in openrouter-stream.ts
- Add supportsReasoning to supportsReasoningEffort check in openrouter-stream
The mocha unit-test harness stubbed @cline/llms with an empty catalog, so the SDK-migrated provider handlers (which read the real catalog) failed ~51 tests under the nightly suite.
- src/test/requires.ts now loads the real @cline/llms by resolving its package directory and requiring the ESM entry by absolute path (Node 22 require(esm)), bypassing the package's import-only exports map. This restores real catalog data to provider unit tests.
- vertexModelSupportsGlobalEndpoint: also match legacy ':' context-window/speed suffixes (e.g. claude-opus-4-7:1m), not just '@' snapshot variants.
- refreshClineModels: derive prompt-cache support from reported input_cache_read pricing for any provider, not just openai/google prefixes.
- Delete the failing tests for the classic provider handlers (bedrock cross-region/native-tool-calling, gemini metadata, cline/openrouter qwen cache, wandb unknown-model). These handlers are only reached via buildApiHandler (explain-changes + commit-message generation) and the provider/catalog domain is owned and tested by the SDK; they will be removed when buildApiHandler is retired.
The nightly publish runs the full webview-ui suite and the Windows extension suite, which surfaced failures the extension-only run does not.
- Harden useProviderModels against a missing providerModelsByProvider map so a partially-mocked ExtensionStateContext no longer crashes the hook.
- Update the SapAiCoreModelPicker and APIOptions specs to provide the provider model-list context the components now read from the SDK catalog, and seed the model ids each test asserts. Removes the now-dead @shared/api sapAiCoreModels mock.
- Compare resolveDataDir() against path.join() instead of a hardcoded POSIX path so the CLINE_DIR fallback test passes on Windows.
Selecting the OpenAI Compatible provider failed with 'Unknown provider "openai"', and manually entered model ids were displayed as the catalog default (gpt-4o).
- Map the extension's 'openai' provider id to the SDK's 'openai-compatible' built-in at the SDK boundary (toSdkProviderId), and convert before handing the provider id to core when building a session config.
- Treat openai-compatible as a custom-model-id provider so model resolution honors a user-entered model id instead of coercing it to the catalog default. Adds providerAllowsCustomModelIds() as the shared signal.
- Bump @cline/core, @cline/llms, @cline/shared, @cline/agents to ^0.0.42 (which registers the openai-compatible built-in) and dedupe the dependency tree.
- Carry the tool name on reconstructed Anthropic-format tool_result blocks to satisfy the SDK's ToolResultContent contract.
- Update tests for the refreshed SDK model catalog (Gemini default).
- Remove porting scree: docs/sdk-model-catalog/* planning docs, TODO-resume-session.md, and stale doc references in code/comments.
- Rewrite before/after narrative comments in the 'eternal now' style; drop transient 'Step N'/'Phase N' labels.
- Remove leftover [HistoryPerf] diagnostic logging and a dead try/catch rethrow; minor readability/naming.
- Restore .clinerules/network.md (still relevant) and update sdk-migration.md to drop dangling references.
- Fix latent circular-init TDZ in openai-codex-models (lazy catalog build).
- toggleRemoteConfigSetting no longer returns a never-resolving promise.
- Move vitest config into apps/vscode and rename script test:sdk -> test:vitest; wire it into CI.
- Repair/reimplement the SDK-adapter vitest suites (auth-service, provider-migration, sdk-task-history) to match current behavior; all 432 tests pass.
Migrates the vscode extension off its hand-curated static model catalogs
in apps/vscode/src/shared/api.ts and on to the @cline/llms SDK as the
single source of truth for provider/model metadata, end-to-end across
the extension host and the webview.
Net impact on the static catalog file:
apps/vscode/src/shared/api.ts: 5092 -> 468 lines (~90% gone).
What changed at each layer
--------------------------
SDK / catalog plumbing (apps/vscode/src/sdk/model-catalog/):
- New `ProviderCatalog.peekModels(providerId)` synchronous cache read.
- `resolveModelInfo` rewritten: committed selection -> catalog peek
-> await catalog.resolveModels on cache miss. No race with a
background warmer; if the catalog truly has nothing, returns
source: "unknown" and the webview renders a neutral loading state.
- `applyHostModelInfoOverrides` is the canonical seam for the few
fields the SDK does not yet carry. Today it carries only the Vertex
`supportsGlobalEndpoint` allowlist (vertex-global-endpoint.ts, with
a TODO to upstream into the SDK).
- `ProviderListing` extended with SDK metadata (`is_popular`,
`popular_rank`, `usage_cost_display`, `capabilities[]`) and plumbed
through proto + conversion.
Extension-host handlers (apps/vscode/src/core/api/providers/):
- New shared helper `apps/vscode/src/shared/sdk-handler-models.ts`:
`getProviderModelFromSdk(providerId, requestedModelId, committedInfo?)`
returns `{ id, info }` from `getProviderCollectionSync` with
Vertex global-endpoint overrides applied.
- 27 handlers converted to a one-liner `getModel()` through that
helper. Per-handler nuances preserved:
* Anthropic: strips `:fast` and `:1m` host-side suffixes before
SDK lookup; carries them back on the returned id so the
per-request betas still flip.
* Bedrock: keeps the custom Application Inference Profile ARN
branch; base-model info from the SDK.
* Cerebras: keeps the `qwen-3-coder-480b-free` -> `qwen-3-coder-480b`
paid alias.
* Qwen / ZAi: SDK has a single catalog each; handlers keep the
regional base-URL switch but no longer fork the catalog.
* Wandb: keeps the "unknown id falls through to safe defaults"
escape hatch via `MODEL_COLLECTIONS_BY_PROVIDER_ID`.
Refresh-models background tasks:
- `refreshBasetenModels`, `refreshGroqModels`, `refreshHuggingFaceModels`
source their offline-fallback catalog from the SDK via
`getProviderCollectionSync` + `adaptSdkModelInfo`. Live fetch path
unchanged; only the seeding/fallback data changed.
Webview (apps/vscode/webview-ui/):
- `useNormalizedApiConfiguration` always routes through gRPC
`resolveModelInfo`. Removed the `isMigratedSdkProvider` /
`MIGRATED_SDK_PROVIDER_IDS` feature flag and the legacy
`normalizeApiConfiguration` switch entirely.
- New `useStaticProviderSelection` hook for the 22 settings
components whose catalog is now SDK-driven, and
`useDynamicProviderSelection` for the 12 dynamic-list pickers
(openrouter, cline, openai-compatible, ollama, lmstudio, requesty,
litellm, hicap, groq, baseten, huggingface, vercel-ai-gateway,
aihubmix, oca, huawei-cloud-maas, dify, fireworks, together,
vscode-lm) so all of them stop calling the legacy switch.
- `ModelInfoView` reads its `isGemini` check via
`useProviderModels("gemini")` instead of importing `geminiModels`.
- `App.stories.tsx` ships a small inline fixture instead of
importing `bedrockModels`.
- `ExtensionStateContext` no longer seeds `groqModelsState` /
`basetenModelsState` from the deleted catalog; the slices start
empty and the SDK-curated catalog is layered in by the pickers at
render time.
Misc:
- `src/utils/model-utils.ts`: `isAnthropicModelId` consults
`MODEL_COLLECTIONS_BY_PROVIDER_ID["anthropic"]` instead of the
deleted `anthropicModels` map.
- `src/shared/storage/provider-keys.ts`: `getProviderDefaultModelId`
no longer hard-codes 24 per-provider defaults. The function now
consults the SDK catalog and only keeps an override map for
providers whose default is intentionally not the SDK default
(openrouter-shared dynamic providers and local-only providers).
- `src/shared/openai-codex-models.ts`: relative path for
`shape-adapter` import so both the extension and webview build
contexts resolve it identically.
Tests retargeted to assert SDK behavior, not static-map shapes
--------------------------------------------------------------
- claude-code, anthropic, bedrock, vertex, wandb, provider-keys
test suites had assertions tied to deleted shapes. Rewrote them
to either assert through the SDK catalog
(anthropic compares against `adaptSdkModelInfo(sdkCollection.models[id])`,
wandb uses the SDK-declared default, etc.) or focus on the
host-side semantics (bedrock's "global endpoint" block now tests
`vertexModelSupportsGlobalEndpoint` directly).
- claude-code test trimmed its 8 `[1m]`/version-pin variants down
to three SDK-shaped cases. The 8 deleted assertions exercised
extension-only model-id derivations that the SDK does not carry;
matching the CLI's behavior was the explicit goal.
- resolveModelInfo test rewritten around the new peek -> await
-> unknown contract.
- proto-lint: added missing `go_package` option to
`proto/cline/remote_config.proto`.
Verification (npm scripts under apps/vscode/):
- npm run protos OK
- npm run check-types OK (apps/vscode + apps/vscode/webview-ui)
- npm run lint OK (biome + proto-lint)
- npm run build:webview OK (tsc -b && vite build)
- node esbuild.mjs OK (dist/extension.js produced)
- Runtime smoke test: 27/27 provider collections resolve from the
SDK with correct model counts, defaults, and usage-cost-display
flags. openai-codex returns cost=hide as expected; every other
provider returns cost=show.