Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .agents/skills/refresh-model-catalog/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ 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.
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. The sync drops an override itself once upstream publishes its own levels. It parses only the `MODEL_EFFORT_OVERRIDES` object literal and preserves neighboring declarations. Keep shared rationale above the declaration; review entry-specific comments after pruning because they may describe removed entries.

### 3. Update display pricing (manual review)

Expand Down
80 changes: 78 additions & 2 deletions .github/scripts/check-commandcode-model-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { tmpdir } from "node:os"
import { join, resolve } from "node:path"
import { pathToFileURL } from "node:url"
import { promisify } from "node:util"
import ts from "typescript"

import {
COMMAND_CODE_CLI_VERSION,
Expand Down Expand Up @@ -40,6 +41,7 @@ function execNpmFileAsync(
}
const CATALOG_SOURCE_PATH = new URL("../../src/commandcode-catalog.ts", import.meta.url)
const README_PATH = new URL("../../README.md", import.meta.url)
const OVERRIDES_SOURCE_PATH = new URL("../../src/commandcode-catalog-overrides.ts", import.meta.url)

export interface CommandCodeModelMetadata {
imageModelIds: readonly string[]
Expand Down Expand Up @@ -379,19 +381,85 @@ function updateDocumentedCatalogVersion(
return contents.replace(pattern, `command-code@${packageVersion}`)
}

/**
* Drop manual effort overrides that upstream now publishes itself.
*
* `tests/test-models.ts` fails while an override duplicates upstream efforts, so
* leaving the removal to a human kept the scheduled workflow red and blocked its
* own pull request. The overrides file is hand-formatted, so this rewrites entries
* and leaves comments, ordering, and still-needed entries untouched.
*/
export function pruneObsoleteEffortOverrides(
contents: string,
upstreamEffortModelIds: readonly string[],
): { contents: string; removedModelIds: readonly string[] } {
const source = ts.createSourceFile("overrides.ts", contents, ts.ScriptTarget.Latest, true)
const upstreamModelIds = new Set(upstreamEffortModelIds)
for (const statement of source.statements) {
if (!ts.isVariableStatement(statement)) continue
for (const declaration of statement.declarationList.declarations) {
if (!ts.isIdentifier(declaration.name) || declaration.name.text !== "MODEL_EFFORT_OVERRIDES")
continue
const map = declaration.initializer
if (!map || !ts.isObjectLiteralExpression(map)) {
throw new Error("MODEL_EFFORT_OVERRIDES must be an object literal")
}
const obsolete = map.properties.filter(
(property) =>
ts.isPropertyAssignment(property) &&
ts.isStringLiteral(property.name) &&
upstreamModelIds.has(property.name.text),
)
const removedModelIds = obsolete.flatMap((property) =>
ts.isPropertyAssignment(property) && ts.isStringLiteral(property.name)
? [property.name.text]
: [],
)
if (obsolete.length === 0) return { contents, removedModelIds }
if (obsolete.length === map.properties.length) {
return {
contents: contents.slice(0, map.getStart(source)) + "{}" + contents.slice(map.end),
removedModelIds: sorted(removedModelIds),
}
}
// Only remove syntax spans, preserving comments and all neighboring declarations.
let updated = contents
for (const property of [...obsolete].reverse()) {
const tokenStart = property.getStart(source)
const lineStart = contents.lastIndexOf("\n", tokenStart - 1) + 1
const start = /^\s*$/.test(contents.slice(lineStart, tokenStart)) ? lineStart : tokenStart
const comma = /^\s*,/.exec(contents.slice(property.end, map.end))
const tokenEnd = property.end + (comma?.[0].length ?? 0)
const newline = /^[ \t]*\r?\n/.exec(contents.slice(tokenEnd))
const end = tokenEnd + (newline?.[0].length ?? 0)
updated = updated.slice(0, start) + updated.slice(end)
}
return { contents: updated, removedModelIds: sorted(removedModelIds) }
}
}
return { contents, removedModelIds: [] }
}

export function updateReadmeCatalogVersion(readme: string, packageVersion: string): string {
return updateDocumentedCatalogVersion(readme, packageVersion, "README")
}

async function writeSynchronizedCatalog(
packageVersion: string,
metadata: CommandCodeModelMetadata,
): Promise<void> {
): Promise<readonly string[]> {
const readme = await readFile(README_PATH, "utf-8")
await Promise.all([
writeFile(CATALOG_SOURCE_PATH, renderCommandCodeCatalog(packageVersion, metadata), "utf-8"),
writeFile(README_PATH, updateReadmeCatalogVersion(readme, packageVersion), "utf-8"),
])

const overrides = await readFile(OVERRIDES_SOURCE_PATH, "utf-8")
const pruned = pruneObsoleteEffortOverrides(overrides, Object.keys(metadata.reasoningEfforts))
if (pruned.removedModelIds.length > 0) {
await writeFile(OVERRIDES_SOURCE_PATH, pruned.contents, "utf-8")
}
return pruned.removedModelIds
}

function metadataReport(
Expand Down Expand Up @@ -510,8 +578,16 @@ async function main(): Promise<void> {
console.log(report)

if (write) {
await writeSynchronizedCatalog(upstreamPackage.packageVersion, upstreamPackage.metadata)
const removedOverrides = await writeSynchronizedCatalog(
upstreamPackage.packageVersion,
upstreamPackage.metadata,
)
console.log(`Synchronized static metadata with command-code@${upstreamPackage.packageVersion}.`)
if (removedOverrides.length > 0) {
console.log(
`Removed ${removedOverrides.length} manual effort override(s) now published upstream: ${removedOverrides.join(", ")}.`,
)
}
return
}

Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/model-metadata.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ on:
paths:
- ".github/scripts/check-commandcode-model-metadata.ts"
- ".github/workflows/model-metadata.yml"
- "README.md"
- "src/commandcode-catalog-overrides.ts"
- "src/commandcode-catalog.ts"
- "src/core.ts"
- "src/models.ts"
- "tests/test-model-metadata-check.ts"
- "tests/test-models.ts"
schedule:
- cron: "17 6 * * *"
workflow_dispatch:
Expand Down Expand Up @@ -58,7 +61,7 @@ jobs:
run: npm run sync:commandcode-catalog | tee commandcode-catalog-report.md
- name: Format and verify synchronized files
run: |
npm run format -- src/commandcode-catalog.ts README.md
npm run format -- src/commandcode-catalog.ts src/commandcode-catalog-overrides.ts README.md
npm run typecheck
npm run test:models
npm run format:check
Expand Down Expand Up @@ -87,4 +90,5 @@ jobs:
assignees: patlux
add-paths: |
src/commandcode-catalog.ts
src/commandcode-catalog-overrides.ts
README.md
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

- Rebind a host's preselected built-in Command Code model to the extension's registered transport at session start, preserving configured endpoints and generate fallback on Oh My Pi.

- Make the daily catalog sync self-healing: it now drops manual effort overrides that upstream has published itself, instead of leaving the removal to a human. The scheduled workflow previously failed on its own guard test, which skipped the pull-request step, so it could never propose the fix. The sync pull request also carries `src/commandcode-catalog-overrides.ts` now.
- Fix the pi end-to-end suite against pi 0.85 and newer, which streams Anthropic Messages through the SDK and appends `?beta=true` to `/v1/messages`. The mock matched the exact URL and answered 404, so every pull request failed while CI installs pi unpinned.

## 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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ Other extensions that stream with the active Command Code model, such as backgro

### 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. 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.
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. The catalog sync removes an override as soon as upstream publishes its own 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.

List Command Code models from the terminal:

Expand Down Expand Up @@ -139,7 +139,7 @@ The following environment variables are intended for tests, local mocks, and com

## Image input

The provider advertises image input only for models marked with the `image` input modality in the official Command Code CLI model catalog. The capability snapshot currently follows `command-code@1.44.0`; unknown models default to text-only until their upstream metadata is reviewed. A daily GitHub Actions job synchronizes the CLI version, image capabilities, reasoning flags, reasoning efforts, and model-specific output limits with the latest published CLI package and opens or updates a reviewable pull request when they change. Pricing remains manually reviewed because temporary promotions and long-context tiers require explicit review.
The provider advertises image input only for models marked with the `image` input modality in the official Command Code CLI model catalog. The capability snapshot currently follows `command-code@1.44.0`; unknown models default to text-only until their upstream metadata is reviewed. A daily GitHub Actions job synchronizes the CLI version, image capabilities, reasoning flags, reasoning efforts, and model-specific output limits with the latest published CLI package, also dropping manual effort overrides that upstream has published itself, and opens or updates a reviewable pull request when they change. Pricing remains manually reviewed because temporary promotions and long-context tiers require explicit review.

For vision-capable models, Pi's native provider adapters forward image blocks from user messages and tool results using the documented OpenAI or Anthropic message schema. Unknown and text-only models remain marked text-only in Pi.

Expand Down
9 changes: 9 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,15 @@ function legacyApiBase(providerApiBase: string): string {
}

export default async function (pi: ExtensionAPI) {
// Hosts may select their built-in Command Code model before loading extensions.
// Rebind that stale selection to our registered transport before the first turn.
pi.on("session_start", async (_event, ctx) => {
if (ctx.model?.provider !== "commandcode") return
const registered = ctx.modelRegistry.find("commandcode", ctx.model.id)
if (registered?.api === COMMAND_CODE_API && ctx.model.api !== COMMAND_CODE_API) {
await pi.setModel(registered)
}
})
const apiBase = process.env.COMMANDCODE_API_BASE ?? DEFAULT_PROVIDER_API_BASE
const modelsUrl = process.env.COMMANDCODE_MODELS_URL ?? DEFAULT_MODELS_URL
const modelsTimeoutMs = getModelsTimeoutMs()
Expand Down
17 changes: 14 additions & 3 deletions src/commandcode-catalog-overrides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,22 @@ import type { CommandCodeReasoningEffort } from "./commandcode-catalog.ts"
*
* `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`.
* merged over the generated catalog at load time. `npm run sync:commandcode-catalog`
* deletes an entry as soon as upstream publishes its own levels, so nothing has to
* be removed by hand.
*
* An entry only takes effect for a model the generated catalog already marks as
* reasoning-capable: `src/core.ts` drops `reasoning_effort` when `model.reasoning`
* is false, and a model missing from `MODEL_REASONING` stays false. Upstream emits
* the flag whenever it emits efforts, so a sync that brings in new efforts brings
* the flag with it.
*
* Keep shared rationale above the declaration. The sync parses this object
* literal and preserves neighboring declarations; review entry-specific comments
* after pruning because they may describe removed entries.
*
* 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.
* Command Code endpoint.
*/
export const MODEL_EFFORT_OVERRIDES: Readonly<
Record<string, readonly CommandCodeReasoningEffort[]>
Expand Down
Loading
Loading