All notable changes to the Cmd Code VSC extension are documented here.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
The last five releases fixed request bodies. This one fixes the response: every tool call was reaching Copilot with no arguments at all, and the extension then answered a successful call with a sentence saying it had failed. Together those produced a model that retried the same call in a loop while narrating the retries.
-
Streamed tool arguments were discarded, so every tool call arrived empty. On
/chat/completions,idandnamearrive once on the first delta and the JSON arguments stream afterwards in fragments. The reader emitted the call on the first delta and readargumentsfrom that same frame, where it is always"". Calls reached Copilot asinput: {raw: ""}— no parameters, including ones the model's own schema marked required.Calls are now accumulated per
indexand emitted onfinish_reason. -
Anthropic had the same defect, unconditionally. A
tool_useblock carriesinput: {}oncontent_block_startwith the arguments following asinput_json_deltafragments. The call was emitted at the start frame, so every Claude tool call arrived asinput: {}.Fragments are accumulated per block index and the call is emitted when the block closes. A non-streaming server's inline input is kept as the fallback.
-
A successful tool call was answered with "returned no content". A turn that only calls a tool has no text in it by definition, so the guard fired and appended to the answer:
Command Code returned no content. See the Command Code log.Reported parts become response content, so VS Code concatenated that into the answer and sent it back on the next request — where the model read it as its own prior turn, and retried. This is the same output-integrity rule as the "Working…" placeholder removed in 0.1.3: anything a provider writes into the content stream is indistinguishable from the model's own words, and permanently so. The guard now fires only when a turn produced neither text nor a tool call.
A turn that genuinely produced nothing still reports, so a hang stays explained.
The same shape on both dialects, and the two decoder fixes are one fix. The earlier releases fixed what we send; this one fixes what we read back, and it was invisible in review because the test that covered split arguments asserted the tool's name and nothing else — so a reader that threw away every argument passed it.
The test now asserts the reassembled payload, and its fixtures are built with
JSON.stringify rather than hand-escaped literals: argument fragments are JSON
nested inside a JSON string, and a mistyped comma there produces an unparsable
frame the reader discards — indistinguishable from a decoder bug, and it cost
three false diagnoses while writing these tests.
npm run check — 520 passed / 0 failed. Each fix was confirmed by restoring
the old behaviour and counting the failures (3, 4 and 1 respectively), so none of
them can pass silently again.
-
A successful reply was reported as a failure. The model answered, every token streamed into the chat, and the turn was then discarded with "Command Code API stream ended before a completion event." The stream reader required a terminal frame before accepting a turn, and the pattern it required — a usage-carrying chunk with no choices — was inferred from documentation rather than observed. A Chat Completions stream signals completion with
finish_reasonon an ordinary final chunk, which is not a distinct event.Completion is now the stream ending. The only failure reported is a stream that produced no frames at all; a truncated one surfaces as the truthful "returned no content" rather than a fabricated protocol error. A working reference provider for this API makes no terminal assertion at all — it consumes the stream and returns when it ends.
The /responses dialect is no longer used by default. Both of the last two
schema defects were in it, and a working reference provider for this same API
does not use it at all.
- Routes prefer
/chat/completions. Every model that declares/responsesalso declares/chat/completions, so coverage is unchanged — 73 of the 82 catalog models now go there, 9 Claude models go to/messages, and/responsesis a fallback for a model that declares it alone. /chat/completionsmessages follow the reference provider's shape: text is the message'scontentas a plain string, an array of parts only when an image is present, a tool call rides astool_calls, and a tool result becomes its ownrole: "tool"message keyed bytool_call_id.- No empty content is ever sent. A message whose parts were all dropped still
carries a text block, because an empty
contentarray is rejected. This is the reference provider's guard, adopted after reading it.
0.3.1, 0.3.2 and 0.3.3 were three schema defects in three releases, all in the
hand-written request path, all in /responses. The root cause was the same each
time: a wire format implemented from its name rather than its specification.
The reference provider for this API uses two dialects and lets the vendor SDKs
serialise them, so it has no hand-written content vocabulary to get wrong. This
release adopts its shape for the one route both share.
- Every API message still failed, with
Invalid input: expected string, received undefined. The text block'stypeis per-dialect: Responses calls itinput_text, while/chat/completionsand/messagesboth usetext. Sendingtextto/responsesleft the required string unreadable — on every message, with no image and no tools involved. /responsesimage blocks were malformed.input_imagetakesimage_urlas a bare string and requiresdetail; the extension was sending{ image_url: { url } }, which is the shape/chat/completionswants and the one route that is not/responses.- Tool parts are now placed where each route expects them. They were being emitted as content
blocks on every dialect. Anthropic wants
tool_use/tool_resultinside the message content; Responses wantsfunction_call/function_call_outputas top-level siblings of the message; Chat Completions wants the result as its ownrole: "tool"message keyed bytool_call_id.
test/api-request-body.test.tsnow asserts the per-dialect text type, image shape, and tool placement on all three routes. Reverting the text type totextfails a test, verified — this is the assertion that was missing when 0.3.2 shipped.
LanguageModelDataPartin thevscodestub exposed its bytes asvalue, while the product matches data parts structurally on adatafield. Every image test was therefore passing against an empty content array — the same failure mode as the bugs above, in the tests themselves.
- Every API message failed with
Invalid input: expected object, received undefined. Tool definitions were sent flat —{ type, name, parameters }— while the schema wants{ type: "function", function: { name, parameters } }. A flat definition carries nofunctionkey at all, which the server reads as undefined. The two shapes were inconsistent within the same file: tool calls were already sent nested, only the definitions were not. - Anthropic's tool shape is not OpenAI's.
/messagestakes tools flat withinput_schema, so the converted definition is reshaped per route rather than sent as-is. toolsis now omitted when there are none. Every documented example carries notoolskey, and an empty array is the likeliest trigger for a schema that expects at least one entry.
test/api-request-body.test.ts, which captures the exact JSON sent on the wire for all three routes and asserts the tool shape, the URL, and that no field isundefined. Verified against the 0.3.1 shape: 8 of its assertions fail when the definitions are reverted to flat, so this cannot regress silently again.LanguageModelToolCallPartandLanguageModelToolResultPartin thevscodetest stub. The provider narrows request content withinstanceofagainst them, and without them the check evaluatesundefined.
Three defects reported against 0.3.0, all confirmed against the live API and a live extension host.
-
Models failed with
Model "…" is not available on this endpoint. The extension routed on "Claude ⇒/messages, else/responses", but the API serves three route sets, not two.GET /provider/v1/models— which is public and needs no auth — reportssupported_endpointsper model, and 9 of our 82 serve/chat/completionsonly:/chat/completions,/responses 67 /messages 10 /chat/completions 9 ← these 400 on /responsesRouting is now read from a generated table transcribed from the server, with
scripts/sync-endpoints.mjsto refresh it. A/chat/completionsrequest builder was added too, because that dialect differs structurally: images areimage_urlparts, and a tool result is its ownrole: "tool"message rather than a block inside the next turn. The generator also caught four wrong rows in the hand-built table — four newer Claude models that the prose rule would have misrouted. -
Pinned models from the CLI group did not appear in the picker. Two causes, both fixed. First, the CLI group advertised
toolCalling: false, and VS Code's Agent session lists only tool-capable models — so a pin could never resolve into the picker. Second, and the deeper problem, model ids weresha256(workspacePath + modelId), so they changed with the folder. VS Code drops any pin whose id is not in the live model cache, which made every pin folder-scoped and silently dead, and left dead ids pinned forever with no way to collect them. Ids are now a hash of the model id alone. The per-folder isolation the salt bought was illusory anyway: the working directory travels per request (RunRequest.cwd), never in the id.Pins made before 0.3.1 must be re-made once. This is a one-time migration.
-
The extension icon did not load in the details tab. The README's
<img src="media/icon.png">is a relative path, which resolves against the extension's installed directory rather than the repo, so it 404s in the webview. It now points at the repository URL. The packaged icon itself was always correct — 128×128 RGBA, declared as aMicrosoft.VisualStudio.Services.Icons.Defaultasset — so this was the README only.
- The CLI group now advertises
toolCalling: true, so its models are selectable in every Copilot session. That is a claim about the model — Command Code models can call tools — not about Copilot driving them, which it still cannot do on that transport. The API group remains where Copilot's own tools and browser control actually run, and the README says so plainly. - The API request is logged with its route before it is sent, so a wrong route is diagnosable from the log alone rather than only from the server's error.
A second provider, real tool support, and image support on both paths.
- The Command Code Provider API as a second provider group (
cmdcode-api, displayed as Command Code API). It talks tohttps://api.commandcode.ai/provider/v1over HTTPS, so there is no process to spawn and the ~3–4 s CLI cold start is gone. - Real tool calling on the API group, including browser control and MCP servers. VS Code's own
tools arrive in
options.toolsand are sent with the request; a tool the model wants comes back as aLanguageModelToolCallPartfor Copilot to execute — this extension never runs a tool itself. Results come back on the next turn asfunction_call_output. - Image support on both providers, on by default. The API sends a proper content block; the CLI
receives a content-addressed staged file that the prompt names, which is how a headless run reads
an image. The CLI is told to read images without asking (
--config imageVisionEnabled=true) only on a turn that actually contains one, because a headless run has no way to prompt. cmdcode.setApiKey— Command Code: Set API Key. Stores the credential in VS Code'sSecretStorage, never insettings.json.cmdcode.imageSupport(defaulttrue),cmdcode.enableCliProvider(defaulttrue),cmdcode.enableApiProvider(defaulttrue) andcmdcode.zeroDataRetention(defaultfalse).- A
catalog-updateissue form, so a model that changes upstream is reported with the evidence needed to fix it: the model id, what the extension shows, what the CLI reports, and both versions. scripts/sync-capabilities.mjs, which regenerates the per-model capability flags from the CLI's own catalog and reports any drift.
- The providers are now displayed as "Command Code CLI" and "Command Code API" rather than the single "Command Code" group, because they are different capabilities rather than two modes of one. Either can be switched off independently.
imageInputis now advertised per model, transcribed from the vendor's own capability catalog. 62 of the 82 models can read images. The previous blanketfalsewas wrong; so would have been a blankettrue— 20 models genuinely cannot.toolCallingis now advertised per provider. It istrueon the API group, where the loop is real, andfalseon the CLI group, where it is not.- The README documents the two providers, images, the capability table, the plan gate, and how to report a catalog change.
- Nothing was dropped silently. Both documented tool constraints are now handled rather than
discovered: remote
mcptools are rewritten totype: "function"(the API rejects them because the upstream would dial the user's server on Command Code's credential), and under zero data retention the tool array is filtered to the documented safe set rather than sent and refused.
- The CLI group is hidden in Copilot's Agent session. VS Code's Agent mode lists only tool-capable models, and the CLI path cannot honestly claim that flag — the CLI runs its tools in-process and never yields to a host. The models remain available in Ask and Chat. This is the same fact as the tool limitation, seen from the picker.
- A Go-plan user has no API access, which is the vendor's design: Go is the only plan without
it. The
403 upgrade_requiredis surfaced verbatim rather than hidden behind a silent fallback to the CLI, because a billing problem should not look like a slower model.
This release changes the extension's marketplace identity. The extension ID changes, so it must
be reinstalled — existing installs of cmdcode.cmdcode are not upgraded automatically.
| Before | After | |
|---|---|---|
| Extension ID | cmdcode.cmdcode |
devparanjay.command-code-provider |
| Display name | Command Code | Command Code Provider |
| Publisher | cmdcode |
devparanjay |
- A rewritten README for the Marketplace and GitHub: a what-it-does summary, requirements, quick start, settings and command tables, feature and performance sections, a privacy and security section, troubleshooting keyed on real log output, and links.
- Trademarks and affiliation section. This is an independent community project, not affiliated with, endorsed by, sponsored by or supported by Command Code. It covers the model names, the third-party marks, the icon's provenance, and a commitment to act on trademark requests.
- A license and warranty section stating AGPL-3.0-or-later and the absence of warranty.
- A new extension icon built from the official Command Code symbol with a
PROVIDERcaption strip, generated from the vendor's ownsymbol.svgbyscripts/make_icon.py. The previous icon was unrelated placeholder artwork, and an intermediate hand-transcription of the SVG paths produced a broken mark; the script now rasterises the real artwork instead. - Tests pinning the three-way identity split — marketplace name, provider vendor id, and
display name — so they cannot be conflated again, and asserting the
cmdcode.*settings and command namespace survives the rename.
- Command titles, the output channel, and the log prefix are now
Command Coderather thanCmd Code, matching the new display name. Command ids are unchanged (cmdcode.showLog,cmdcode.copyDiagnostics,cmdcode.restartProvider), so keybindings andsettings.jsonkeep working. - Settings are unchanged and still namespaced
cmdcode.*. The namespace is independent of the publisher, so renaming it would have silently reset every user's configuration. - The provider still registers under the vendor id
cmdcodeand still displays as Command Code in the model picker. Only the extension's marketplace identity changed; the provider name the user sees is deliberately unaffected.
-
Every reply was prefixed with
Working…. The extension reported aLanguageModelTextPart('Working…')before the first token to fill the ~3–4 s the CLI takes to start, but every part reported toprogressbecomes response content and cannot be retracted — so the placeholder stayed in the transcript permanently:Working…Hello! I'm working in the cmdcode-vsc VS Code extension…The stable API offers no non-content channel for it.
LanguageModelResponsePartis a closed union ofLanguageModelTextPart | LanguageModelToolResultPart | LanguageModelToolCallPart— all content — andProvideLanguageModelChatResponseOptionscarries no progress handle. The placeholder is removed rather than reworded; Copilot renders its own pending state while it waits.
cmdcode.showThinkingPlaceholder. It only ever controlled whether a fabricated token was prepended to the answer, and the answer should never contain one. The setting is gone from the manifest,CmdCodeConfig,CONFIG_DEFAULTSand the README rather than left as a no-op.
- The provider now emits nothing before the model's first token. Tests assert this directly, including that a silent run produces exactly one part (the explanation) and no padding.
-
The models now appear in Copilot Chat's model picker. They advertised
toolCalling: false, and VS Code filters the model list for the Agent session through(m) => (m.capabilities?.agentMode ?? true) && !!m.capabilities?.toolCalling. With the flag false, every one of the 82 models was dropped before the picker rendered — so the provider registered fine, the models were listed in the Language Models window, and the picker was empty in the mode Copilot opens in.toolCallingis nowtrue. This is a true statement about the models: the Command Code CLI executes tools in-process (tool_running→execGuarded→tool_completed) and never yields for a host to run one. The extension still emits noLanguageModelToolCallPartand still ignoresoptions.tools, so Copilot sends no tool schemas and tool calls do not appear in the chat's tool UI. Nothing is dropped — Copilot simply never asks.imageInputstaysfalseand is unchanged: the prompt path renders text parts only, so an image part would be discarded. Advertising vision would be a lie untilbuildPromptforwardsLanguageModelDataPart.
- README and the
catalog-to-chat.tscapability comment now describe what the flag does and does not imply, instead of claiming "no tool calling". - Tests assert
toolCalling: trueagainst the projection, with the Agent-mode filter quoted, so the flag cannot silently regress back to false.
- The extension registered no models in VS Code. It called
vscode.lm.registerLanguageModelChatProvider('cmdcode', …)without declaring thecmdcodevendor incontributes.languageModelChatProviders, and VS Code only admits a vendor that its own manifest declares. The call was rejected in the extension host withChat model provider uses UNKNOWN vendor cmdcode., so the model picker stayed empty — while the output channel still loggedCmd Code: activated, because the rejection lands in another process afteractivate()resolves. The CLI was found and working throughout; only the registration was refused.
contributes.languageModelChatProvidersnow declares thecmdcodevendor, which is what puts Cmd Code's models in the picker and gives the extension its marketplacelanguage-modelstag.- The
onLanguageModelChatProvider:cmdcodeactivation event, which VS Code generates from that contribution, alongsideonStartupFinished. - A regression test that reads
VENDOR_IDout ofsrc/types.tsand asserts the manifest declares it. The manifest and the source were two unconnected string literals, so nothing failed until the provider was run inside a real VS Code. - The
Machine Learningcategory, matching the other language model providers. - The provider now appears in the model picker as Command Code rather than
Cmd Code. The vendor id stayscmdcode, so no model id changes. - The extension itself is listed as Command Code in the extensions view.
- Concurrent turns no longer orphan a live CLI process.
cancel()signalled only the newest of several overlapping runs, so an earlier run kept streaming into a chat the user had navigated away from. cancel()can no longer deadlock. It waited on the completion promise of the run it had just sentSIGTERM, so a CLI slow to honour the signal left the caller waiting on the process it was trying to stop.- One run finishing no longer destroys another run's
SIGKILLescalation, which could leak a process permanently whenever the CLI was slow to honourSIGTERM. - The pre-spawn cancel latch is now scoped to a run that has no live child, so a cancellation issued during one run no longer suppresses an unrelated later turn.
- Initial scaffold: extension manifest, TypeScript/vitest/tsup toolchain, and a shared
vscodetest stub. - Declares the
cmdcodelanguage model vendor, the sixcmdcode.*settings, and thecmdcode.showLog,cmdcode.copyDiagnosticsandcmdcode.restartProvidercommands.
Provider registration, the model catalog, CLI transport, and the chat provider land in subsequent releases.