feat(providers): add ClinePass and Command Code GOAT - #3927
Conversation
Coding-plan users need first-class setup without maintaining stale model catalogs. Dynamic discovery keeps ClinePass and Command Code routing aligned with their authoritative provider sources. Lore-id: 66fc75f6 Constraint: provider model catalogs must remain dynamic and Claude Command models must use Anthropic Messages Rejected: hardcoded provider model lists | they become stale and can route newly added Claude models incorrectly Confidence: high Scope-risk: wide Reversibility: simple Tested: focused provider onboarding, ranking, setup, slash-command, registry discovery, routing, schema sync, package typecheck, and live catalog probes Not-tested: authenticated Command Code inference because no credential was available; Cline inference completion because the account was quota-limited
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 286f0e8e8b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| contextWindow: item.context_length ?? referenceModel?.contextWindow ?? UNK_CONTEXT_WINDOW, | ||
| maxTokens: referenceModel?.maxTokens ?? UNK_MAX_TOKENS, |
There was a problem hiding this comment.
Cap discovered output tokens to the advertised context
When an OpenAI-compatible /models endpoint reports a smaller context_length for a known model—such as a local proxy exposing a bundled Claude ID with a reduced context—the resulting model combines that server limit with the bundled model's potentially larger maxTokens. This can advertise an impossible output allowance and, for Anthropic-routed models, cause buildParams to derive a max_tokens value that exceeds the endpoint's total context; clamp the reference output limit to the discovered context or use endpoint-provided output metadata.
Useful? React with 👍 / 👎.
| const response = await fetch(modelsUrl, { | ||
| headers, | ||
| signal: AbortSignal.timeout(250), | ||
| headers: requestHeaders, | ||
| signal: AbortSignal.timeout(5_000), |
There was a problem hiding this comment.
Preserve the short timeout for local model discovery
When implicit LM Studio discovery is uncached and its endpoint accepts a connection but does not respond—or LM_STUDIO_BASE_URL points at a black-holed host—an awaited refresh now stalls for five seconds because lm-studio is dispatched through this same method. This path previously had a 250 ms budget and is awaited by model-selection and task-execution flows, so use the longer timeout only for remote providers such as Command Code while retaining a short timeout for optional local discovery.
Useful? React with 👍 / 👎.
286f0e8 to
29c045d
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 29c045d10e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for (const [prefix, routedApi] of Object.entries(providerConfig.discovery.apiByModelPrefix ?? {})) { | ||
| if (modelId.startsWith(prefix) && prefix.length > matchedPrefixLength) { | ||
| api = routedApi; |
There was a problem hiding this comment.
Reapply prefix routing to cached models
When apiByModelPrefix is edited while this provider has a fresh discovery cache, the normal refresh() path uses online-if-uncached and returns the cached model objects without invoking this resolver. Those objects retain the API selected by the previous configuration, so a model can continue using Chat Completions instead of Anthropic Messages (or vice versa) for up to the 24-hour cache lifetime. Reapply the current prefix rules while normalizing cached models, or include these rules in cache invalidation.
Useful? React with 👍 / 👎.
29c045d to
26f1ec0
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 26f1ec0d9d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| async #discoverModelsDevProvider(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> { | ||
| const baseUrl = providerConfig.baseUrl; |
There was a problem hiding this comment.
Rebind cached models.dev rows to the live base URL
When a models-dev provider's configured baseUrl changes while its 24-hour discovery cache is fresh, each cached model retains the old URL assigned here. #loadCachedDiscoverableModels only restores the live URL for openai-models-list and lm-studio, and online-if-uncached then accepts these rows while recording evidence for the new endpoint, so inference—and its API key—can continue going to the previous endpoint until the cache expires. Normalize cached models-dev rows against the current provider URL as well.
Useful? React with 👍 / 👎.
26f1ec0 to
4149e2b
Compare
yazzang-homelab
left a comment
There was a problem hiding this comment.
Independent architect review. This is the first human review — the three prior entries are bot comments.
Both presets are well-formed and the two discovery strategies are correctly matched to their providers.
cline-pass https://api.cline.bot/api/v1 CLINE_API_KEY
discovery: models-dev (modelsDevProvider: "cline-pass")
commandcode-goat https://api.commandcode.ai/provider/v1 CMD_API_KEY
discovery: openai-models-list (apiByModelPrefix)
Using models-dev for ClinePass (which publishes to the upstream catalog) and openai-models-list for Command Code (which does not, but exposes /models) is the right split — a single strategy would have forced one of them into a hand-maintained model list that goes stale silently.
Details I checked rather than assumed:
addApiCompatibleProvider({ preset: "cline-pass", models: ["custom"] })rejects. A discovery-backed preset must not also accept a caller-supplied model list, or the two sources drift and the user cannot tell which won. Asserting the rejection, not just the happy path, is what makes that a contract instead of a convention.modelsis assertedtoBeUndefined()in the written config for the discovery preset — so nothing is materialized at setup time that would later shadow discovery.- Aliases are distinct and non-overlapping:
clinepass/cline→cline-pass, andcommandcode/command-code/goat→commandcode-goat. No alias resolves into both, which is the failure that makesgjc setup <alias>nondeterministic. - Ranking is a 2-line addition and both
provider-ranking.test.tsandprovider-ranking.redteam.test.tswere updated. A new provider entering the ranking table without a red-team update is the usual way an autorouting tier quietly changes for unrelated models. - CHANGELOG is intact (312,528 bytes vs 312,259 on
dev— grew by this PR's entry). Worth stating: twelve open PRs currently have a truncated or emptied changelog (#3942), and this is not one of them. - Merges cleanly into current
dev. Also note this PR editsdocs/models.mdanddocs/environment-variables.mdbut carries nodocs-index.generated.tschange — confirmation that #3932's untracking is working as intended on a real docs-touching PR.
One thing to verify before merge, which I cannot check from here: both baseUrl values are third-party endpoints that will receive user API keys and prompt content. api.cline.bot/api/v1 and api.commandcode.ai/provider/v1 should each be confirmed against the vendor's published documentation — a typo'd or squatted host in a shipped preset is a credential-exfiltration path that no test will catch, because every test here asserts the constant matches itself. If the PR body cites the vendor docs URL for each, that closes it.
Related: CMD_API_KEY is a notably generic environment variable name for a shipped preset. It is plausible a user already has CMD_API_KEY set for something unrelated, which would silently authenticate against Command Code. COMMANDCODE_API_KEY would collide with nothing. If the vendor documents CMD_API_KEY then matching them is correct and this is just worth a note; if it is our choice, the specific name is safer.
gajae.pr-review-verdict.v1 merge-approved sha256:4149e2bc21580a2988cad7e6eba253e7a17cbd52 reviewer:architect evidence:read of provider-presets.json entries, provider-onboarding.test.ts:610-690, and provider-ranking diffs at this head; merge-tree vs origin/dev clean; CHANGELOG 312528 bytes intact
4149e2b to
f93155c
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f93155c094
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| const discovered: Model<Api>[] = []; | ||
| for (const [catalogId, value] of Object.entries(catalogProvider.models)) { | ||
| if (!isRecord(value) || value.tool_call !== true || value.status === "deprecated") continue; |
There was a problem hiding this comment.
Remove models omitted by refreshed catalogs
When a previously discovered ClinePass model is later marked deprecated or removed, an online refresh filters it out here, but #refreshRuntimeDiscoveries merges only the returned rows into the existing this.#models array and never removes provider rows absent from the new result; its discovered.length === 0 path also returns without changing the catalog. Consequently, deprecated or withdrawn models remain selectable until the process restarts, despite a successful live refresh. Replace the provider's prior discovered slice when publishing an authoritative result.
Useful? React with 👍 / 👎.
f93155c to
d6fbc1c
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d6fbc1cbb0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| thinking: referenceModel?.thinking, | ||
| input: referenceModel?.input ?? ["text"], | ||
| output: referenceModel?.output, | ||
| cost: referenceModel?.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, |
There was a problem hiding this comment.
Avoid inheriting upstream prices for proxy catalogs
When an OpenAI-compatible /models response contains an ID from the bundled catalog—such as Command Code's Claude models or a known model loaded through LM Studio—this copies the first-party model's per-token prices even though the discovered endpoint may be subscription-backed or local. calculateCost subsequently uses model.cost for these non-OpenAI providers, so session totals and exports report charges that were never incurred; keep discovery pricing at zero unless the endpoint or a provider-specific override supplies it.
Useful? React with 👍 / 👎.
…ntract Dev CI at 4f6e860 fails provider-onboarding-wizard-redteam on a stale assertion: it expected "At least one model id is required" while addApiCompatibleProvider throws the post-Yeachan-Heo#3927 public message "At least one model id or model discovery is required." Update the empty-models red-team expectation to the exact intended substring of that contract. No runtime change. Lore-id: 9c4e1a02 Confidence: high Scope-risk: narrow Reversibility: trivial Tested: bun test packages/coding-agent/test/provider-onboarding-wizard-redteam.test.ts (7 pass / 0 fail) Not-tested: full Dev CI shard matrix
Summary
cline-passandcommandcode-goatprovider presetsclaude-*model through Anthropic Messages and all other models through OpenAI Chat CompletionsProvider contracts
ClinePass
https://api.cline.bot/api/v1CLINE_API_KEY/chat/completionscline-passprovider data fromhttps://models.dev/api.jsonCline's inference API does not expose a working ClinePass
/modelsroute, so this follows Cline's own upstream catalog-generation source.Command Code GOAT
https://api.commandcode.ai/provider/v1CMD_API_KEY/modelsclaude-*:/messagesviaanthropic-messages/chat/completionsviaopenai-completionsVerification
bun --cwd=packages/coding-agent run checkbun run check:schemasgit diff --checkLive QA
cline-pass/*models through models.dev/api/v1/models: 404, confirming the alternate catalog source is requiredAuthenticated Command Code inference was not exercised because the authorized remote configuration did not contain a Command Code credential.