Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## Unreleased

- Enable thinking for `deepseek/deepseek-v4.1-flash` and future catalog-gap models. `src/commandcode-catalog.ts` is pinned to a Command Code CLI release, so a model added upstream afterwards has no reasoning flag until the next catalog sync, and Pi forwarded no `reasoning_effort` for it. A new `MODEL_REASONING_OVERRIDES` hook next to the existing effort overrides carries the flag, and Pi and Oh My Pi now expose `low`, `high`, and `max` for that model (#90).

### Contributors

- @Fu3rte — reported the catalog-gap reasoning failure and proposed the override hook (#90).

## 0.6.4 - 2026-09-03

- Refresh the generated Command Code capability catalog from `command-code@1.40.1` to `command-code@1.44.0`, adding current image-input, reasoning, effort, and output-limit metadata for newly published models.
Expand Down
25 changes: 25 additions & 0 deletions src/commandcode-catalog-overrides.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
import type { CommandCodeReasoningEffort } from "./commandcode-catalog.ts"

/**
* Manual reasoning-flag policy for models the official CLI marks as
* reasoning-capable but that are missing from the pinned catalog.
*
* `src/commandcode-catalog.ts` is generated from the CLI package and must stay
* byte-identical to upstream so the daily drift check works, so a model added
* upstream after the last sync has no reasoning flag until the catalog is
* regenerated. `MODEL_REASONING` gates everything downstream
* (`src/core.ts` drops `reasoning_effort` without it and `index.ts` derives
* `compat.supportsReasoningEffort` from the effort list), so an efforts-only
* override is not enough.
*
* Add a model only when the upstream CLI bundle marks it `reasoning:!0`;
* remove it once the generated catalog carries the flag.
*/
export const MODEL_REASONING_OVERRIDES: Readonly<Record<string, true>> = {
// Command Code CLI 1.53.0: DEEPSEEK_V4_1_FLASH {reasoning:!0,
// reasoningEfforts:["low","high","max"]}; absent from the pinned catalog.
"deepseek/deepseek-v4.1-flash": true,
}

/**
* Manual reasoning-effort policy for models the official CLI marks as
* reasoning-capable without publishing selectable efforts.
Expand All @@ -22,4 +43,8 @@ export const MODEL_EFFORT_OVERRIDES: Readonly<
"meta/muse-spark-1.2-contributor": ["minimal", "low", "medium", "high", "xhigh"],
"meta/muse-spark-1.3": ["minimal", "low", "medium", "high", "xhigh"],
"meta/muse-spark-1.3-contributor": ["minimal", "low", "medium", "high", "xhigh"],

// Command Code CLI 1.53.0 publishes these efforts upstream; the pinned
// catalog predates the model and therefore carries neither flag nor efforts.
"deepseek/deepseek-v4.1-flash": ["low", "high", "max"],
}
15 changes: 12 additions & 3 deletions src/models.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"
import { dirname } from "node:path"

import { MODEL_EFFORT_OVERRIDES } from "./commandcode-catalog-overrides.ts"
import {
MODEL_EFFORT_OVERRIDES,
MODEL_REASONING_OVERRIDES,
} from "./commandcode-catalog-overrides.ts"
import {
MODEL_EFFORTS as CATALOG_MODEL_EFFORTS,
MODEL_INPUT_MODALITIES,
MODEL_MAX_OUTPUT_TOKENS,
MODEL_REASONING,
MODEL_REASONING as CATALOG_MODEL_REASONING,
type CommandCodeInputType,
type CommandCodeReasoningEffort,
} from "./commandcode-catalog.ts"
Expand All @@ -17,7 +20,13 @@ export const MODEL_EFFORTS: Readonly<Record<string, readonly CommandCodeReasonin
...MODEL_EFFORT_OVERRIDES,
}

export { MODEL_INPUT_MODALITIES, MODEL_MAX_OUTPUT_TOKENS, MODEL_REASONING }
/** Upstream CLI reasoning flags with the manual overrides merged over them. */
export const MODEL_REASONING: Readonly<Record<string, true>> = {
...CATALOG_MODEL_REASONING,
...MODEL_REASONING_OVERRIDES,
}

export { MODEL_INPUT_MODALITIES, MODEL_MAX_OUTPUT_TOKENS }
export type { CommandCodeInputType }

export const DEFAULT_PROVIDER_API_BASE = "https://api.commandcode.ai/provider/v1"
Expand Down
22 changes: 21 additions & 1 deletion tests/test-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import { tmpdir } from "node:os"
import { join } from "node:path"
import { describe, it } from "node:test"

import { MODEL_EFFORT_OVERRIDES } from "../src/commandcode-catalog-overrides.ts"
import {
MODEL_EFFORT_OVERRIDES,
MODEL_REASONING_OVERRIDES,
} from "../src/commandcode-catalog-overrides.ts"
import {
COMMAND_CODE_CLI_VERSION,
MODEL_EFFORTS as CATALOG_MODEL_EFFORTS,
MODEL_REASONING as CATALOG_MODEL_REASONING,
} from "../src/commandcode-catalog.ts"
import {
apiForModelId,
Expand Down Expand Up @@ -216,6 +220,22 @@ describe("commandCodeModelsFromApiResponse()", () => {
}
})

it("merges manual reasoning overrides over the generated catalog", () => {
assert.ok(Object.keys(MODEL_REASONING_OVERRIDES).length > 0)
for (const modelId of Object.keys(MODEL_REASONING_OVERRIDES)) {
assert.equal(
CATALOG_MODEL_REASONING[modelId],
undefined,
`${modelId} now has an upstream reasoning flag; drop the manual override`,
)
assert.equal(MODEL_REASONING[modelId], true)
assert.ok(
MODEL_EFFORTS[modelId]?.length,
`${modelId} needs selectable efforts; the endpoint rejects a bare reasoning flag`,
)
}
})

it("builds separate canonical pi and OMP metadata", () => {
for (const [modelId, efforts] of Object.entries(MODEL_EFFORTS)) {
const metadata = thinkingMetadataForModel(modelId)
Expand Down
24 changes: 23 additions & 1 deletion tests/test-stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { after, before, beforeEach, describe, it } from "node:test"

import { COMMAND_CODE_CLI_VERSION } from "../src/commandcode-catalog.ts"
import type { AssistantMessageEvent } from "../src/core.ts"
import { MODEL_EFFORTS, thinkingLevelMapForEfforts } from "../src/models.ts"
import { MODEL_EFFORTS, MODEL_REASONING, thinkingLevelMapForEfforts } from "../src/models.ts"
import {
collectEvents,
createTestDeps,
Expand Down Expand Up @@ -852,6 +852,28 @@ describe("streamCommandCode — request serialization", () => {
assert.equal(objectAt(server.lastRequestBody(), ["params", "reasoning_effort"]), "max")
})

it("forwards a supported Pi reasoning level for a catalog-gap model", async () => {
server.mockResponse({
type: "success",
events: [JSON.stringify({ type: "finish", finishReason: "stop" })],
})
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
// Model added upstream after the pinned catalog: its metadata comes only
// from the manual overrides, so a missing flag or effort list drops the field.
const modelId = "deepseek/deepseek-v4.1-flash"
const model = makeModel({
id: modelId,
reasoning: MODEL_REASONING[modelId] === true,
thinkingLevelMap: thinkingLevelMapForEfforts(MODEL_EFFORTS[modelId]),
})

await collectEvents(
streamCommandCode(model, makeContext(), { apiKey: "mock-key", reasoning: "max" }),
)

assert.equal(objectAt(server.lastRequestBody(), ["params", "reasoning_effort"]), "max")
})

it("omits reasoning_effort for off, unsupported, and unknown reasoning levels", async () => {
const model = makeModel({
id: "deepseek/deepseek-v4-flash",
Expand Down
Loading