From 7b0b66ff5b691cbeadc28b566ef22a4939d42101 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Fri, 5 Jun 2026 08:53:25 -0500 Subject: [PATCH 1/2] fix(google): preserve TTS model during sync --- packages/core/src/sync/index.ts | 33 +++++++-- packages/core/src/sync/providers/google.ts | 69 ++++++++++++++++++- .../models/gemini-3-pro-image-preview.toml | 3 + 3 files changed, 98 insertions(+), 7 deletions(-) diff --git a/packages/core/src/sync/index.ts b/packages/core/src/sync/index.ts index 19cc36af4..957811a0a 100644 --- a/packages/core/src/sync/index.ts +++ b/packages/core/src/sync/index.ts @@ -44,13 +44,18 @@ export interface SyncProvider { name: string; modelsDir: string; skipCreates?: boolean; + preserveMissing?: string[]; sourceID?(model: SourceModel): string; skippedNotice?(ids: string[]): string[]; fetchModels(): Promise; parseModels(raw: unknown): SourceModel[]; translateModel( model: SourceModel, - context: { existing(id: string): ExistingModel | undefined }, + context: { + existing(id: string): ExistingModel | undefined; + authored(id: string): ExistingModel | undefined; + base(id: string): ExistingModel | undefined; + }, ): { id: string; model: SyncedModel } | undefined; } @@ -106,6 +111,7 @@ export async function syncProvider( const existing = await readExisting(provider.modelsDir); const sourceModels = provider.parseModels(await provider.fetchModels()); const desired = new Map; content: string }>(); + const preserveMissing = new Set(provider.preserveMissing ?? []); const skippedRemote: string[] = []; for (const sourceModel of sourceModels) { @@ -113,6 +119,12 @@ export async function syncProvider( existing(id) { return existing.get(`${id}.toml`)?.toml; }, + authored(id) { + return existing.get(`${id}.toml`)?.authored; + }, + base(id) { + return existing.get(`${id}.toml`)?.base; + }, }); if (translated === undefined) { if (provider.skipCreates) skippedRemote.push(provider.sourceID?.(sourceModel) ?? "unknown"); @@ -182,8 +194,10 @@ export async function syncProvider( for (const relativePath of existing.keys()) { if (desired.has(relativePath)) continue; - if (options.newOnly) { - console.log(`Skipping removal in new-only mode: ${relativePath}`); + const id = relativePath.slice(0, -5); + if (options.newOnly || preserveMissing.has(id)) { + const mode = options.newOnly ? "new-only mode" : "preserve list"; + console.log(`Skipping removal in ${mode}: ${relativePath}`); unchanged++; continue; } @@ -235,6 +249,7 @@ async function readExisting(modelsDir: string) { const existing = new Map(); let modelMetadata: Record> | undefined; @@ -251,11 +266,21 @@ async function readExisting(modelsDir: string) { if (authored.base_model !== undefined && modelMetadata === undefined) { modelMetadata = await readModelMetadata(modelsDir); } + const base = authored.base_model === undefined + ? undefined + : resolveBaseModel( + { + base_model: authored.base_model, + base_model_omit: authored.base_model_omit, + }, + modelMetadata ?? {}, + path.join(modelsDir, file), + ); const toml = authored.base_model === undefined ? authored : resolveBaseModel(authored, modelMetadata ?? {}, path.join(modelsDir, file)); - existing.set(file, { authored, toml, symlink }); + existing.set(file, { authored, toml, base, symlink }); } return existing; diff --git a/packages/core/src/sync/providers/google.ts b/packages/core/src/sync/providers/google.ts index bf79dac45..a59feaca7 100644 --- a/packages/core/src/sync/providers/google.ts +++ b/packages/core/src/sync/providers/google.ts @@ -32,6 +32,8 @@ export const google = { name: "Google", modelsDir: "providers/google/models", skipCreates: true, + // v1beta/models omits Gemini-TTS models served by Cloud TTS / Vertex AI. + preserveMissing: ["gemini-2.5-flash-tts"], sourceID(model) { return model.name.replace(/^models\//, ""); }, @@ -81,12 +83,17 @@ export const google = { return { id, - model: buildModel(model, existing), + model: buildModel(model, existing, context.authored(id), context.base(id)), }; }, } satisfies SyncProvider; -function buildModel(model: GoogleModel, existing: ExistingModel): SyncedModel { +function buildModel( + model: GoogleModel, + existing: ExistingModel, + authored: ExistingModel | undefined, + base: ExistingModel | undefined, +): SyncedModel { const name = existing.name; const releaseDate = existing.release_date; const lastUpdated = existing.last_updated; @@ -111,7 +118,7 @@ function buildModel(model: GoogleModel, existing: ExistingModel): SyncedModel { throw new Error(`Google model ${model.name} has incomplete local TOML metadata required for sync`); } - return { + const synced = { base_model: existing.base_model, base_model_omit: existing.base_model_omit, name: model.displayName ?? name, @@ -138,4 +145,60 @@ function buildModel(model: GoogleModel, existing: ExistingModel): SyncedModel { }, modalities, }; + + if (authored?.base_model === undefined || base === undefined) return synced; + + // Keep base_model TOMLs minimal by writing only provider-specific fields and API overrides. + const result: SyncedModel = { + base_model: authored.base_model, + base_model_omit: authored.base_model_omit, + cost: authored.cost, + provider: authored.provider, + experimental: authored.experimental, + reasoning_options: authored.reasoning_options, + interleaved: authored.interleaved, + status: authored.status, + }; + + setOverride(result, "name", synced.name, base.name); + setOverride(result, "family", synced.family, base.family); + setOverride(result, "release_date", synced.release_date, base.release_date); + setOverride(result, "last_updated", synced.last_updated, base.last_updated); + setOverride(result, "attachment", synced.attachment, base.attachment); + setOverride(result, "reasoning", synced.reasoning, base.reasoning); + setOverride(result, "temperature", synced.temperature, base.temperature); + setOverride(result, "tool_call", synced.tool_call, base.tool_call); + setOverride(result, "structured_output", synced.structured_output, base.structured_output); + setOverride(result, "knowledge", synced.knowledge, base.knowledge); + setOverride(result, "open_weights", synced.open_weights, base.open_weights); + setOverride(result, "modalities", synced.modalities, base.modalities); + + const limitOverride = { + input: synced.limit.input !== base.limit?.input ? synced.limit.input : undefined, + context: synced.limit.context !== base.limit?.context ? synced.limit.context : undefined, + output: synced.limit.output !== base.limit?.output ? synced.limit.output : undefined, + }; + if ( + limitOverride.input !== undefined + || limitOverride.context !== undefined + || limitOverride.output !== undefined + ) { + result.limit = limitOverride; + } + + return result; +} + +function setOverride( + target: SyncedModel, + key: K, + value: SyncedModel[K], + baseValue: ExistingModel[K], +) { + if (sameValue(value, baseValue)) return; + target[key] = value; +} + +function sameValue(left: unknown, right: unknown) { + return JSON.stringify(left) === JSON.stringify(right); } diff --git a/providers/google/models/gemini-3-pro-image-preview.toml b/providers/google/models/gemini-3-pro-image-preview.toml index 98a4ae2c5..a68249299 100644 --- a/providers/google/models/gemini-3-pro-image-preview.toml +++ b/providers/google/models/gemini-3-pro-image-preview.toml @@ -3,3 +3,6 @@ base_model = "google/gemini-3-pro-image-preview" [cost] input = 2 output = 120 + +[limit] +context = 131_072 From f139bcd6aab20334b5aa18f9e0fdeabdb63af76e Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Fri, 5 Jun 2026 09:04:30 -0500 Subject: [PATCH 2/2] refactor(google): simplify sync preserve logic --- packages/core/src/sync/index.ts | 17 +---- packages/core/src/sync/providers/google.ts | 82 ++++++---------------- 2 files changed, 24 insertions(+), 75 deletions(-) diff --git a/packages/core/src/sync/index.ts b/packages/core/src/sync/index.ts index 957811a0a..eaeb0ebe7 100644 --- a/packages/core/src/sync/index.ts +++ b/packages/core/src/sync/index.ts @@ -54,7 +54,6 @@ export interface SyncProvider { context: { existing(id: string): ExistingModel | undefined; authored(id: string): ExistingModel | undefined; - base(id: string): ExistingModel | undefined; }, ): { id: string; model: SyncedModel } | undefined; } @@ -122,9 +121,6 @@ export async function syncProvider( authored(id) { return existing.get(`${id}.toml`)?.authored; }, - base(id) { - return existing.get(`${id}.toml`)?.base; - }, }); if (translated === undefined) { if (provider.skipCreates) skippedRemote.push(provider.sourceID?.(sourceModel) ?? "unknown"); @@ -249,7 +245,6 @@ async function readExisting(modelsDir: string) { const existing = new Map(); let modelMetadata: Record> | undefined; @@ -266,21 +261,11 @@ async function readExisting(modelsDir: string) { if (authored.base_model !== undefined && modelMetadata === undefined) { modelMetadata = await readModelMetadata(modelsDir); } - const base = authored.base_model === undefined - ? undefined - : resolveBaseModel( - { - base_model: authored.base_model, - base_model_omit: authored.base_model_omit, - }, - modelMetadata ?? {}, - path.join(modelsDir, file), - ); const toml = authored.base_model === undefined ? authored : resolveBaseModel(authored, modelMetadata ?? {}, path.join(modelsDir, file)); - existing.set(file, { authored, toml, base, symlink }); + existing.set(file, { authored, toml, symlink }); } return existing; diff --git a/packages/core/src/sync/providers/google.ts b/packages/core/src/sync/providers/google.ts index a59feaca7..56920c962 100644 --- a/packages/core/src/sync/providers/google.ts +++ b/packages/core/src/sync/providers/google.ts @@ -83,7 +83,7 @@ export const google = { return { id, - model: buildModel(model, existing, context.authored(id), context.base(id)), + model: buildModel(model, existing, context.authored(id)), }; }, } satisfies SyncProvider; @@ -92,7 +92,6 @@ function buildModel( model: GoogleModel, existing: ExistingModel, authored: ExistingModel | undefined, - base: ExistingModel | undefined, ): SyncedModel { const name = existing.name; const releaseDate = existing.release_date; @@ -118,7 +117,28 @@ function buildModel( throw new Error(`Google model ${model.name} has incomplete local TOML metadata required for sync`); } - const synced = { + if (authored?.base_model !== undefined) { + const result: SyncedModel = { ...authored, base_model: authored.base_model }; + const limitOverride = { ...authored.limit }; + if (model.inputTokenLimit !== limit.context) { + limitOverride.context = model.inputTokenLimit; + } + if (model.outputTokenLimit !== limit.output) { + limitOverride.output = model.outputTokenLimit; + } + if ( + limitOverride.context !== undefined + || limitOverride.input !== undefined + || limitOverride.output !== undefined + ) { + result.limit = limitOverride; + } else { + delete result.limit; + } + return result; + } + + return { base_model: existing.base_model, base_model_omit: existing.base_model_omit, name: model.displayName ?? name, @@ -145,60 +165,4 @@ function buildModel( }, modalities, }; - - if (authored?.base_model === undefined || base === undefined) return synced; - - // Keep base_model TOMLs minimal by writing only provider-specific fields and API overrides. - const result: SyncedModel = { - base_model: authored.base_model, - base_model_omit: authored.base_model_omit, - cost: authored.cost, - provider: authored.provider, - experimental: authored.experimental, - reasoning_options: authored.reasoning_options, - interleaved: authored.interleaved, - status: authored.status, - }; - - setOverride(result, "name", synced.name, base.name); - setOverride(result, "family", synced.family, base.family); - setOverride(result, "release_date", synced.release_date, base.release_date); - setOverride(result, "last_updated", synced.last_updated, base.last_updated); - setOverride(result, "attachment", synced.attachment, base.attachment); - setOverride(result, "reasoning", synced.reasoning, base.reasoning); - setOverride(result, "temperature", synced.temperature, base.temperature); - setOverride(result, "tool_call", synced.tool_call, base.tool_call); - setOverride(result, "structured_output", synced.structured_output, base.structured_output); - setOverride(result, "knowledge", synced.knowledge, base.knowledge); - setOverride(result, "open_weights", synced.open_weights, base.open_weights); - setOverride(result, "modalities", synced.modalities, base.modalities); - - const limitOverride = { - input: synced.limit.input !== base.limit?.input ? synced.limit.input : undefined, - context: synced.limit.context !== base.limit?.context ? synced.limit.context : undefined, - output: synced.limit.output !== base.limit?.output ? synced.limit.output : undefined, - }; - if ( - limitOverride.input !== undefined - || limitOverride.context !== undefined - || limitOverride.output !== undefined - ) { - result.limit = limitOverride; - } - - return result; -} - -function setOverride( - target: SyncedModel, - key: K, - value: SyncedModel[K], - baseValue: ExistingModel[K], -) { - if (sameValue(value, baseValue)) return; - target[key] = value; -} - -function sameValue(left: unknown, right: unknown) { - return JSON.stringify(left) === JSON.stringify(right); }