(model: OpenRouterModel)
| 137 | } |
| 138 | |
| 139 | function generateModelMetaString(model: OpenRouterModel): string { |
| 140 | const inputModalities = model.architecture.input_modalities |
| 141 | .map(mapInputModality) |
| 142 | .filter((m): m is InputModality => m !== null) |
| 143 | const outputModalities = model.architecture.output_modalities |
| 144 | .map(mapInputModality) |
| 145 | .filter((m): m is InputModality => m !== null) |
| 146 | // OpenRouter uses `~prefix/name` to denote routing aliases (e.g. |
| 147 | // `~anthropic/claude-haiku-latest`). The model ID itself is preserved as a |
| 148 | // string literal so users can pass it to `chat({ model: ... })`. The leading |
| 149 | // `~` is mapped to `_` only for the derived constant name so it's a valid |
| 150 | // JavaScript identifier. |
| 151 | const constName = model.id |
| 152 | .replaceAll('~', '_') |
| 153 | .replaceAll('/', '-') |
| 154 | .replaceAll('-', '_') |
| 155 | .replaceAll('.', '_') |
| 156 | .replaceAll(':', '_') |
| 157 | .toUpperCase() |
| 158 | // Safety net: if a future OpenRouter ID quirk produces a non-identifier |
| 159 | // constant name, fail loudly here instead of letting prettier choke on the |
| 160 | // generated file later in the pipeline. |
| 161 | if (!/^[A-Z_][A-Z0-9_]*$/.test(constName)) { |
| 162 | throw new Error( |
| 163 | `Generated constant name is not a valid JS identifier: ${JSON.stringify( |
| 164 | constName, |
| 165 | )} (from OpenRouter model.id ${JSON.stringify( |
| 166 | model.id, |
| 167 | )}). Extend the constName sanitiser to handle this case.`, |
| 168 | ) |
| 169 | } |
| 170 | // Ensure at least 'text' is present |
| 171 | if (!inputModalities.includes('text')) { |
| 172 | inputModalities.unshift('text') |
| 173 | } |
| 174 | const nonChatFamilies = ['lyria', 'veo', 'imagen', 'sora', 'dall-e', 'tts'] |
| 175 | const isNonChat = nonChatFamilies.some((f) => |
| 176 | model.id.toLowerCase().includes(f), |
| 177 | ) |
| 178 | if (outputModalities.includes('text') && !isNonChat) { |
| 179 | chatModels.add(`${constName}.id`) |
| 180 | } |
| 181 | if (outputModalities.includes('image')) { |
| 182 | imageModels.add(`${constName}.id`) |
| 183 | } |
| 184 | if (outputModalities.includes('video')) { |
| 185 | videoModels.add(`${constName}.id`) |
| 186 | } |
| 187 | const inputPrice = convertPricing(model.pricing.prompt) |
| 188 | const outputPrice = convertPricing(model.pricing.completion) |
| 189 | const imagePrice = convertPricing(model.pricing.image ?? '0', true) |
| 190 | const inputCacheReadPrice = convertPricing( |
| 191 | model.pricing.input_cache_read ?? '0', |
| 192 | ) |
| 193 | const inputCacheWritePrice = convertPricing( |
| 194 | model.pricing.input_cache_write ?? '0', |
| 195 | ) |
| 196 |
nothing calls this directly
no test coverage detected