From d3c9832d28030a8b8d106ab3987089a819e67375 Mon Sep 17 00:00:00 2001 From: Patrick Wozniak Date: Wed, 2 Sep 2026 00:03:45 +0200 Subject: [PATCH] feat(models): move manual reasoning efforts into a catalog override PR #69 added efforts for meta/muse-spark-* directly to the generated catalog, which the drift check flags and the daily sync job reverts. Keep src/commandcode-catalog.ts byte-identical to upstream and merge a separate src/commandcode-catalog-overrides.ts over it at load time. A test fails as soon as upstream publishes efforts for an overridden model so the override gets removed instead of shadowing the CLI catalog. Verified against the live endpoint: pi --thinking xhigh sends reasoning_effort="xhigh" for meta/muse-spark-1.2-contributor and the request succeeds; --thinking off sends none. Closes #69 --- .agents/skills/refresh-model-catalog/SKILL.md | 2 ++ CHANGELOG.md | 1 + README.md | 2 +- src/commandcode-catalog-overrides.ts | 23 ++++++++++++++++ src/commandcode-catalog.ts | 3 --- src/models.ts | 11 ++++++-- tests/test-models.ts | 26 ++++++++++++++++++- 7 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 src/commandcode-catalog-overrides.ts diff --git a/.agents/skills/refresh-model-catalog/SKILL.md b/.agents/skills/refresh-model-catalog/SKILL.md index 8a598f5..25140ba 100644 --- a/.agents/skills/refresh-model-catalog/SKILL.md +++ b/.agents/skills/refresh-model-catalog/SKILL.md @@ -32,6 +32,8 @@ npm run sync:commandcode-catalog Regenerates `src/commandcode-catalog.ts` and bumps the documented CLI version in `README.md`. Review the diff; the catalog also lists reasoning models without selectable efforts. +Never add efforts to the generated file by hand. Manual effort policy for reasoning models that upstream ships without levels lives in `src/commandcode-catalog-overrides.ts` and is merged at load time. When the sync report lists a model from that file under "New effort metadata", remove its override; `tests/test-models.ts` fails until you do. + ### 3. Update display pricing (manual review) Fetch and compare against `src/pricing.ts`: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e85f6b..36a8883 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Expose selectable thinking levels (`minimal`, `low`, `medium`, `high`, `xhigh`) for `meta/muse-spark-1.1`, `meta/muse-spark-1.2`, and `meta/muse-spark-1.2-contributor` through a manual catalog override, so `/thinking` and `Shift+Tab` no longer stay locked on `off` for these reasoning models. - Start from the cached model catalog and refresh it in the background instead of blocking host startup on the catalog request; a first start without a cache still waits for the live catalog. - Register the `commandcode-custom` API in the `@earendil-works/pi-ai/compat` registry so sibling extensions that stream with the active Command Code model no longer fail with `No API provider registered for api: commandcode-custom` on plain pi. - Assert structural catalog invariants in the model tests so the daily catalog sync no longer fails on every upstream change. diff --git a/README.md b/README.md index 427822a..1d2633b 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ Open `/model` and select one of the models provided by Command Code. Model avail ### Reasoning support -Reasoning capability and selectable effort levels follow the official CLI catalog independently. Models can therefore be marked as reasoning-capable even when Command Code chooses their depth automatically; for those models (for example `meta/muse-spark-1.1`, `meta/muse-spark-1.2`, and `meta/muse-spark-1.2-contributor`) the thinking level stays `off` because the CLI itself sends no effort parameter and Command Code has not published selectable levels. Models with explicit effort support register a model-specific `thinkingLevelMap`, so pi and OMP expose only valid levels. Pi's native OpenAI- and Anthropic-compatible providers translate the selected level for Provider API accounts; the existing Command Code generate transport sends the matching `reasoning_effort` for Go accounts. +Reasoning capability and selectable effort levels follow the official CLI catalog independently. Models can therefore be marked as reasoning-capable even when Command Code chooses their depth automatically. Models with explicit effort support register a model-specific `thinkingLevelMap`, so pi and OMP expose only valid levels. For a few reasoning models the CLI catalog ships no effort levels although the endpoint accepts `reasoning_effort`; `src/commandcode-catalog-overrides.ts` adds a manual level set for those (currently `meta/muse-spark-1.1`, `meta/muse-spark-1.2`, and `meta/muse-spark-1.2-contributor`) on top of the generated catalog, and the tests fail once upstream publishes its own levels so the override gets removed. Pi's native OpenAI- and Anthropic-compatible providers translate the selected level for Provider API accounts; the existing Command Code generate transport sends the matching `reasoning_effort` for Go accounts. List Command Code models from the terminal: diff --git a/src/commandcode-catalog-overrides.ts b/src/commandcode-catalog-overrides.ts new file mode 100644 index 0000000..6c6ab36 --- /dev/null +++ b/src/commandcode-catalog-overrides.ts @@ -0,0 +1,23 @@ +import type { CommandCodeReasoningEffort } from "./commandcode-catalog.ts" + +/** + * Manual reasoning-effort policy for models the official CLI marks as + * reasoning-capable without publishing selectable efforts. + * + * `src/commandcode-catalog.ts` is generated from the CLI package and must stay + * byte-identical to upstream so the daily drift check works. Entries here are + * merged over the generated catalog at load time and are not touched by + * `npm run sync:commandcode-catalog`. + * + * Add a model only when the effort parameter is known to be accepted by the + * Command Code endpoint; remove it once the CLI catalog ships its own efforts. + */ +export const MODEL_EFFORT_OVERRIDES: Readonly< + Record +> = { + // Meta Muse Spark: the CLI ships no effort levels, but the endpoint accepts + // `reasoning_effort` for these models and other hosts expose the same set. + "meta/muse-spark-1.1": ["minimal", "low", "medium", "high", "xhigh"], + "meta/muse-spark-1.2": ["minimal", "low", "medium", "high", "xhigh"], + "meta/muse-spark-1.2-contributor": ["minimal", "low", "medium", "high", "xhigh"], +} diff --git a/src/commandcode-catalog.ts b/src/commandcode-catalog.ts index d0846e7..19db48f 100644 --- a/src/commandcode-catalog.ts +++ b/src/commandcode-catalog.ts @@ -133,9 +133,6 @@ export const MODEL_EFFORTS: Readonly> = { + ...CATALOG_MODEL_EFFORTS, + ...MODEL_EFFORT_OVERRIDES, +} + +export { MODEL_INPUT_MODALITIES, MODEL_MAX_OUTPUT_TOKENS, MODEL_REASONING } export type { CommandCodeInputType } export const DEFAULT_PROVIDER_API_BASE = "https://api.commandcode.ai/provider/v1" diff --git a/tests/test-models.ts b/tests/test-models.ts index c5573dc..821a2e6 100644 --- a/tests/test-models.ts +++ b/tests/test-models.ts @@ -4,7 +4,11 @@ import { tmpdir } from "node:os" import { join } from "node:path" import { describe, it } from "node:test" -import { COMMAND_CODE_CLI_VERSION } from "../src/commandcode-catalog.ts" +import { MODEL_EFFORT_OVERRIDES } from "../src/commandcode-catalog-overrides.ts" +import { + COMMAND_CODE_CLI_VERSION, + MODEL_EFFORTS as CATALOG_MODEL_EFFORTS, +} from "../src/commandcode-catalog.ts" import { apiForModelId, baseUrlForModel, @@ -192,6 +196,26 @@ describe("commandCodeModelsFromApiResponse()", () => { } }) + it("merges manual effort overrides over the generated catalog", () => { + const validEfforts = new Set(["minimal", "low", "medium", "high", "xhigh", "max"]) + assert.ok(Object.keys(MODEL_EFFORT_OVERRIDES).length > 0) + for (const [modelId, efforts] of Object.entries(MODEL_EFFORT_OVERRIDES)) { + assert.equal(MODEL_REASONING[modelId], true, `${modelId} override needs a reasoning flag`) + assert.equal( + CATALOG_MODEL_EFFORTS[modelId], + undefined, + `${modelId} now has upstream efforts; drop the manual override`, + ) + assert.ok(efforts.length > 0) + assert.ok(efforts.every((effort) => validEfforts.has(effort))) + assert.deepEqual(MODEL_EFFORTS[modelId], efforts) + assert.deepEqual(thinkingMetadataForModel(modelId)?.thinking?.efforts, efforts) + } + for (const [modelId, efforts] of Object.entries(CATALOG_MODEL_EFFORTS)) { + assert.deepEqual(MODEL_EFFORTS[modelId], efforts, `${modelId} upstream efforts changed`) + } + }) + it("builds separate canonical pi and OMP metadata", () => { for (const [modelId, efforts] of Object.entries(MODEL_EFFORTS)) { const metadata = thinkingMetadataForModel(modelId)