Spaces:
Runtime error
Runtime error
| // Sync the cursor models list in open-sse/config/providerRegistry.ts from | |
| // cursor-agent's runtime model list. Triggers an intentional invalid --model | |
| // invocation so cursor-agent prints "Available models: ..." on stderr. | |
| // | |
| // Usage: | |
| // node scripts/ad-hoc/sync-cursor-models.mjs # spawn cursor-agent and apply | |
| // node scripts/ad-hoc/sync-cursor-models.mjs --dry-run # print proposed block, don't write | |
| // node scripts/ad-hoc/sync-cursor-models.mjs --from-stdin # read the error message from stdin | |
| import { spawnSync } from "node:child_process"; | |
| import { readFileSync, writeFileSync } from "node:fs"; | |
| import { fileURLToPath } from "node:url"; | |
| import { dirname, resolve } from "node:path"; | |
| const __dirname = dirname(fileURLToPath(import.meta.url)); | |
| const REGISTRY_PATH = resolve(__dirname, "..", "open-sse", "config", "providerRegistry.ts"); | |
| const args = new Set(process.argv.slice(2)); | |
| const DRY_RUN = args.has("--dry-run"); | |
| const FROM_STDIN = args.has("--from-stdin"); | |
| function readSource() { | |
| if (FROM_STDIN) return readFileSync(0, "utf8"); | |
| // cursor-agent prints "Available models: ..." to stderr and exits non-zero. | |
| const r = spawnSync("cursor-agent", ["--model", "--help"], { encoding: "utf8" }); | |
| return `${r.stdout || ""}\n${r.stderr || ""}`; | |
| } | |
| // `auto` is a CLI-side abstraction (cursor-agent resolves it locally before | |
| // sending) and `composer-*` targets cursor's edit/composer endpoint, neither | |
| // of which work via the chat RPC. Filter them so the dashboard never offers | |
| // them to the chat path. Pass --include-unsupported to keep them. | |
| function isUnsupportedChatModel(id) { | |
| if (id === "auto") return true; | |
| if (id.startsWith("composer-")) return true; | |
| return false; | |
| } | |
| function parseModelIds(text) { | |
| const m = text.match(/Available models:\s*([^\n]+)/); | |
| if (!m) throw new Error("Could not find 'Available models:' line in cursor-agent output"); | |
| const includeUnsupported = args.has("--include-unsupported"); | |
| return m[1] | |
| .split(",") | |
| .map((s) => s.trim()) | |
| .filter(Boolean) | |
| .filter((id) => includeUnsupported || !isUnsupportedChatModel(id)); | |
| } | |
| const SEGMENT_OVERRIDES = { | |
| gpt: "GPT", | |
| claude: "Claude", | |
| gemini: "Gemini", | |
| grok: "Grok", | |
| kimi: "Kimi", | |
| composer: "Composer", | |
| opus: "Opus", | |
| sonnet: "Sonnet", | |
| haiku: "Haiku", | |
| codex: "Codex", | |
| mini: "Mini", | |
| nano: "Nano", | |
| max: "Max", | |
| high: "High", | |
| low: "Low", | |
| medium: "Medium", | |
| xhigh: "XHigh", | |
| none: "None", | |
| fast: "Fast", | |
| thinking: "Thinking", | |
| extra: "Extra", | |
| spark: "Spark", | |
| preview: "Preview", | |
| flash: "Flash", | |
| pro: "Pro", | |
| }; | |
| // Pretty-print an id by: | |
| // 1) collapsing claude-NAME-X-Y dotted version (e.g. claude-opus-4-7 → claude-opus-4.7) | |
| // 2) splitting on '-' | |
| // 3) applying SEGMENT_OVERRIDES; falling back to capitalize-first | |
| function humanize(id) { | |
| if (id === "auto") return "Auto (Server Picks)"; | |
| // Collapse "X-Y" numeric suffix in claude-foo-X-Y- patterns into "X.Y" | |
| const collapsed = id.replace(/(\d+)-(\d+)(?=-|$)/g, "$1.$2"); | |
| const parts = collapsed.split("-"); | |
| const labelled = parts.map((p) => { | |
| if (SEGMENT_OVERRIDES[p]) return SEGMENT_OVERRIDES[p]; | |
| if (/^\d/.test(p)) return p; // leave version numbers / "k2.5" alone | |
| return p.charAt(0).toUpperCase() + p.slice(1); | |
| }); | |
| return labelled.join(" "); | |
| } | |
| function buildModelsArrayLines(ids) { | |
| const seen = new Set(); | |
| const out = []; | |
| for (const id of ids) { | |
| if (seen.has(id)) continue; | |
| seen.add(id); | |
| out.push(` { id: ${JSON.stringify(id)}, name: ${JSON.stringify(humanize(id))} },`); | |
| } | |
| return out.join("\n"); | |
| } | |
| function replaceCursorModels(source, modelsBlock) { | |
| // Match the `cursor:` provider entry and replace just its `models: [ ... ],` array. | |
| const re = /(\n cursor:\s*\{[\s\S]*?\n models:\s*\[)([\s\S]*?)(\n \],)/; | |
| if (!re.test(source)) throw new Error("Could not locate cursor.models array in registry"); | |
| return source.replace(re, `$1\n${modelsBlock}$3`); | |
| } | |
| const ids = parseModelIds(readSource()); | |
| const block = buildModelsArrayLines(ids); | |
| if (DRY_RUN) { | |
| console.log(block); | |
| process.exit(0); | |
| } | |
| const before = readFileSync(REGISTRY_PATH, "utf8"); | |
| const after = replaceCursorModels(before, block); | |
| if (before === after) { | |
| console.log("No changes — cursor models already in sync."); | |
| process.exit(0); | |
| } | |
| writeFileSync(REGISTRY_PATH, after); | |
| console.log(`Updated ${ids.length} cursor models in ${REGISTRY_PATH}`); | |