diff --git a/packages/opencode/src/plugin.ts b/packages/opencode/src/plugin.ts index 79c7ccc..cf1f8c7 100644 --- a/packages/opencode/src/plugin.ts +++ b/packages/opencode/src/plugin.ts @@ -1464,7 +1464,7 @@ export const createAntigravityPlugin = (providerId: string) => async ( return { config: async (opencodeConfig: Record) => { - applyAntigravityProviderCatalog(opencodeConfig, providerId); + applyAntigravityProviderCatalog(opencodeConfig, providerId, config); const mutableConfig = opencodeConfig as Record & { command?: Record; }; @@ -3851,16 +3851,29 @@ type OpencodeMutableConfig = Record & { }>; }; -function applyAntigravityProviderCatalog(config: Record, providerId: string): void { - const mutableConfig = config as OpencodeMutableConfig; +function applyAntigravityProviderCatalog( + opencodeConfig: Record, + providerId: string, + pluginConfig: AntigravityConfig +): void { + const mutableConfig = opencodeConfig as OpencodeMutableConfig; mutableConfig.provider ??= {}; const providerConfig = mutableConfig.provider[providerId] ?? {}; + + // Merge order (lowest to highest priority): + // 1. Built-in defaults: OPENCODE_MODEL_DEFINITIONS + // 2. Decoupled models (from antigravity.json / antigravity-models.json): pluginConfig.models + // 3. User's main opencode.json models (preserves backwards compatibility / custom overrides) providerConfig.models = { - ...(providerConfig.models ?? {}), ...OPENCODE_MODEL_DEFINITIONS, + ...(pluginConfig.models ?? {}), + ...(providerConfig.models ?? {}), }; - providerConfig.whitelist = getAntigravityOpencodeModelIds(); + + // Whitelist should be the union of all registered models so they aren't pruned by OpenCode + providerConfig.whitelist = Object.keys(providerConfig.models); + mutableConfig.provider[providerId] = providerConfig; } diff --git a/packages/opencode/src/plugin/config/loader.ts b/packages/opencode/src/plugin/config/loader.ts index 514fb0f..0c986ef 100644 --- a/packages/opencode/src/plugin/config/loader.ts +++ b/packages/opencode/src/plugin/config/loader.ts @@ -112,12 +112,56 @@ function mergeConfigs( // Main Loader // ============================================================================= -/** - * Load the complete configuration. - * - * @param directory - The project directory (for project-level config) - * @returns Fully resolved configuration - */ +function stripJsonCommentsAndTrailingCommas(json: string): string { + return json + .replace( + /\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, + (match: string, group: string | undefined) => (group ? "" : match) + ) + .replace(/,(\s*[}\]])/g, "$1"); +} + +function loadModelsFile(path: string): Record | null { + try { + if (!existsSync(path)) { + return null; + } + const content = readFileSync(path, "utf-8"); + const parsed = JSON.parse(stripJsonCommentsAndTrailingCommas(content)); + if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) { + return parsed; + } + return null; + } catch (error) { + log.warn("Failed to load decoupled models file", { path, error: String(error) }); + return null; + } +} + +export function loadDecoupledModels(directory: string): Record { + let mergedModels: Record = {}; + + const configDir = getConfigDir(); + const userJsonPath = join(configDir, "antigravity-models.json"); + const userJsoncPath = join(configDir, "antigravity-models.jsonc"); + const projectJsonPath = join(directory, ".opencode", "antigravity-models.json"); + const projectJsoncPath = join(directory, ".opencode", "antigravity-models.jsonc"); + + // User level (prefer jsonc if both exist) + const userModels = loadModelsFile(existsSync(userJsoncPath) ? userJsoncPath : userJsonPath); + if (userModels) { + mergedModels = { ...mergedModels, ...userModels }; + } + + // Project level (prefer jsonc if both exist) - overrides user level + const projectModels = loadModelsFile(existsSync(projectJsoncPath) ? projectJsoncPath : projectJsonPath); + if (projectModels) { + mergedModels = { ...mergedModels, ...projectModels }; + } + + return mergedModels; +} + export function loadConfig(directory: string): AntigravityConfig { // Start with defaults let config: AntigravityConfig = { ...DEFAULT_CONFIG }; @@ -136,6 +180,15 @@ export function loadConfig(directory: string): AntigravityConfig { config = mergeConfigs(config, projectConfig); } + // Load decoupled models from antigravity-models.json(c) and merge into config.models + const decoupledModels = loadDecoupledModels(directory); + if (Object.keys(decoupledModels).length > 0) { + config.models = { + ...(config.models ?? {}), + ...decoupledModels, + }; + } + return config; } diff --git a/packages/opencode/src/plugin/config/schema.ts b/packages/opencode/src/plugin/config/schema.ts index 1ebef0a..1682792 100644 --- a/packages/opencode/src/plugin/config/schema.ts +++ b/packages/opencode/src/plugin/config/schema.ts @@ -503,6 +503,11 @@ export const AntigravityConfigSchema = z.object({ */ auto_update: z.boolean().default(true), + /** + * Decoupled model definitions to inject. + */ + models: z.record(z.string(), z.any()).optional(), + }); export type AntigravityConfig = z.infer;