Compare commits

...

2 Commits

Author SHA1 Message Date
Aiden Cline f139bcd6aa refactor(google): simplify sync preserve logic 2026-06-05 09:04:30 -05:00
Aiden Cline 7b0b66ff5b fix(google): preserve TTS model during sync 2026-06-05 08:53:25 -05:00
3 changed files with 45 additions and 5 deletions
+13 -3
View File
@@ -44,13 +44,17 @@ export interface SyncProvider<SourceModel> {
name: string;
modelsDir: string;
skipCreates?: boolean;
preserveMissing?: string[];
sourceID?(model: SourceModel): string;
skippedNotice?(ids: string[]): string[];
fetchModels(): Promise<unknown>;
parseModels(raw: unknown): SourceModel[];
translateModel(
model: SourceModel,
context: { existing(id: string): ExistingModel | undefined },
context: {
existing(id: string): ExistingModel | undefined;
authored(id: string): ExistingModel | undefined;
},
): { id: string; model: SyncedModel } | undefined;
}
@@ -106,6 +110,7 @@ export async function syncProvider<SourceModel>(
const existing = await readExisting(provider.modelsDir);
const sourceModels = provider.parseModels(await provider.fetchModels());
const desired = new Map<string, { model: z.infer<typeof SyncedAuthoredModel>; content: string }>();
const preserveMissing = new Set(provider.preserveMissing ?? []);
const skippedRemote: string[] = [];
for (const sourceModel of sourceModels) {
@@ -113,6 +118,9 @@ export async function syncProvider<SourceModel>(
existing(id) {
return existing.get(`${id}.toml`)?.toml;
},
authored(id) {
return existing.get(`${id}.toml`)?.authored;
},
});
if (translated === undefined) {
if (provider.skipCreates) skippedRemote.push(provider.sourceID?.(sourceModel) ?? "unknown");
@@ -182,8 +190,10 @@ export async function syncProvider<SourceModel>(
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;
}
+29 -2
View File
@@ -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,16 @@ export const google = {
return {
id,
model: buildModel(model, existing),
model: buildModel(model, existing, context.authored(id)),
};
},
} satisfies SyncProvider<GoogleModel>;
function buildModel(model: GoogleModel, existing: ExistingModel): SyncedModel {
function buildModel(
model: GoogleModel,
existing: ExistingModel,
authored: ExistingModel | undefined,
): SyncedModel {
const name = existing.name;
const releaseDate = existing.release_date;
const lastUpdated = existing.last_updated;
@@ -111,6 +117,27 @@ function buildModel(model: GoogleModel, existing: ExistingModel): SyncedModel {
throw new Error(`Google model ${model.name} has incomplete local TOML metadata required for sync`);
}
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,
@@ -3,3 +3,6 @@ base_model = "google/gemini-3-pro-image-preview"
[cost]
input = 2
output = 120
[limit]
context = 131_072