From 817c593e12c79249bc0268b448ad162f047cb53e Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 7 Feb 2026 19:51:15 +0900 Subject: [PATCH] refactor(migration): split model and category helpers (#1561) Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus --- src/shared/migration/agent-category.ts | 60 ++++++++++++++++++++++++++ src/shared/migration/model-versions.ts | 49 +++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 src/shared/migration/agent-category.ts create mode 100644 src/shared/migration/model-versions.ts diff --git a/src/shared/migration/agent-category.ts b/src/shared/migration/agent-category.ts new file mode 100644 index 000000000..31eb078a3 --- /dev/null +++ b/src/shared/migration/agent-category.ts @@ -0,0 +1,60 @@ +/** + * @deprecated LEGACY MIGRATION ONLY + * + * This map exists solely for migrating old configs that used hardcoded model strings. + * It maps legacy model strings to semantic category names, allowing users to migrate + * from explicit model configs to category-based configs. + * + * DO NOT add new entries here. New agents should use: + * - Category-based config (preferred): { category: "unspecified-high" } + * - Or inherit from OpenCode's config.model + * + * This map will be removed in a future major version once migration period ends. + */ +export const MODEL_TO_CATEGORY_MAP: Record = { + "google/gemini-3-pro": "visual-engineering", + "google/gemini-3-flash": "writing", + "openai/gpt-5.2": "ultrabrain", + "anthropic/claude-haiku-4-5": "quick", + "anthropic/claude-opus-4-6": "unspecified-high", + "anthropic/claude-sonnet-4-5": "unspecified-low", +} + +export function migrateAgentConfigToCategory(config: Record): { + migrated: Record + changed: boolean +} { + const { model, ...rest } = config + if (typeof model !== "string") { + return { migrated: config, changed: false } + } + + const category = MODEL_TO_CATEGORY_MAP[model] + if (!category) { + return { migrated: config, changed: false } + } + + return { + migrated: { category, ...rest }, + changed: true, + } +} + +export function shouldDeleteAgentConfig( + config: Record, + category: string +): boolean { + const { DEFAULT_CATEGORIES } = require("../../tools/delegate-task/constants") + const defaults = DEFAULT_CATEGORIES[category] + if (!defaults) return false + + const keys = Object.keys(config).filter((k) => k !== "category") + if (keys.length === 0) return true + + for (const key of keys) { + if (config[key] !== (defaults as Record)[key]) { + return false + } + } + return true +} diff --git a/src/shared/migration/model-versions.ts b/src/shared/migration/model-versions.ts new file mode 100644 index 000000000..f5ac88955 --- /dev/null +++ b/src/shared/migration/model-versions.ts @@ -0,0 +1,49 @@ +/** + * Model version migration map: old full model strings → new full model strings. + * Used to auto-upgrade hardcoded model versions in user configs when the plugin + * bumps to newer model versions. + * + * Keys are full "provider/model" strings. Only openai and anthropic entries needed. + */ +export const MODEL_VERSION_MAP: Record = { + "openai/gpt-5.2-codex": "openai/gpt-5.3-codex", + "anthropic/claude-opus-4-5": "anthropic/claude-opus-4-6", +} + +function migrationKey(oldModel: string, newModel: string): string { + return `model-version:${oldModel}->${newModel}` +} + +export function migrateModelVersions( + configs: Record, + appliedMigrations?: Set +): { migrated: Record; changed: boolean; newMigrations: string[] } { + const migrated: Record = {} + let changed = false + const newMigrations: string[] = [] + + for (const [key, value] of Object.entries(configs)) { + if (value && typeof value === "object" && !Array.isArray(value)) { + const config = value as Record + if (typeof config.model === "string" && MODEL_VERSION_MAP[config.model]) { + const oldModel = config.model + const newModel = MODEL_VERSION_MAP[oldModel] + const mKey = migrationKey(oldModel, newModel) + + // Skip if this migration was already applied (user may have reverted) + if (appliedMigrations?.has(mKey)) { + migrated[key] = value + continue + } + + migrated[key] = { ...config, model: newModel } + changed = true + newMigrations.push(mKey) + continue + } + } + migrated[key] = value + } + + return { migrated, changed, newMigrations } +}