diff --git a/scripts/convert-openrouter-models.ts b/scripts/convert-openrouter-models.ts index e5967a975..5c20f6558 100644 --- a/scripts/convert-openrouter-models.ts +++ b/scripts/convert-openrouter-models.ts @@ -130,12 +130,30 @@ function generateModelMetaString(model: OpenRouterModel): string { const outputModalities = model.architecture.output_modalities .map(mapInputModality) .filter((m): m is InputModality => m !== null) + // OpenRouter uses `~prefix/name` to denote routing aliases (e.g. + // `~anthropic/claude-haiku-latest`). The model ID itself is preserved as a + // string literal so users can pass it to `chat({ model: ... })`. The leading + // `~` is mapped to `_` only for the derived constant name so it's a valid + // JavaScript identifier. const constName = model.id + .replaceAll('~', '_') .replaceAll('/', '-') .replaceAll('-', '_') .replaceAll('.', '_') .replaceAll(':', '_') .toUpperCase() + // Safety net: if a future OpenRouter ID quirk produces a non-identifier + // constant name, fail loudly here instead of letting prettier choke on the + // generated file later in the pipeline. + if (!/^[A-Z_][A-Z0-9_]*$/.test(constName)) { + throw new Error( + `Generated constant name is not a valid JS identifier: ${JSON.stringify( + constName, + )} (from OpenRouter model.id ${JSON.stringify( + model.id, + )}). Extend the constName sanitiser to handle this case.`, + ) + } // Ensure at least 'text' is present if (!inputModalities.includes('text')) { inputModalities.unshift('text')