feat: auto-sync provider models from OpenRouter API#433
feat: auto-sync provider models from OpenRouter API#433AlemTuzlak wants to merge 22 commits intomainfrom
Conversation
Use replacer functions in String.replace() to prevent $-character interpretation, and use non-greedy regex for array matching to handle potential ] characters in comments.
…noise - Use OpenRouter's actual input_modalities instead of blindly copying reference model's modalities (fixes text-only models claiming image support) - Filter modalities to only include types valid for each provider's interface - Add blocklist for non-chat model families (lyria, veo, imagen, sora, dall-e, tts) - Swap field order to supports-before-pricing matching existing convention - CI only creates PR when package files actually changed (not just openrouter.models.ts)
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughA new automated workflow is introduced that fetches and syncs OpenRouter API model metadata daily into provider-specific configuration files. The system includes a GitHub Actions workflow, TypeScript scripts for fetching and synchronizing models, and updates to model metadata across multiple provider packages with additions, removals, and capability changes. Changes
Sequence DiagramsequenceDiagram
participant GHA as GitHub Actions
participant Fetch as fetch-openrouter<br/>models.ts
participant OpenRouter as OpenRouter API
participant Sync as sync-provider<br/>models.ts
participant ProviderMeta as Provider<br/>model-meta.ts
participant Git as Git/PR Creation
GHA->>GHA: Trigger (daily or manual)
GHA->>Fetch: Run pnpm generate:models:fetch
Fetch->>OpenRouter: GET /api/v1/models
OpenRouter-->>Fetch: Model metadata array
Fetch->>Fetch: Validate, filter, sort
Fetch->>Fetch: Generate scripts/openrouter.models.ts
GHA->>Sync: Run pnpm regenerate:models<br/>+ sync-provider-models.ts
Sync->>Sync: Load last-run timestamp
Sync->>ProviderMeta: Read current provider models
Sync->>Sync: Filter by provider ID, age, modality
Sync->>ProviderMeta: Insert new model constants
Sync->>ProviderMeta: Update export arrays/maps
Sync->>Sync: Write updated model-meta.ts
Sync->>Sync: Record changed packages
Sync->>Sync: Update .sync-models-last-run
Sync->>Sync: Generate .changeset entry
Sync->>Git: git add, commit, force-push
Git-->>Git: Push to automated/sync-models branch
Git->>Git: Check for existing PR
Git-->>Git: Create PR if none exists
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🚀 Changeset Version Preview5 package(s) bumped directly, 5 bumped as dependents. 🟩 Patch bumps
|
|
View your CI Pipeline Execution ↗ for commit 1ec4c40
☁️ Nx Cloud last updated this comment at |
@tanstack/ai
@tanstack/ai-anthropic
@tanstack/ai-client
@tanstack/ai-code-mode
@tanstack/ai-code-mode-skills
@tanstack/ai-devtools-core
@tanstack/ai-elevenlabs
@tanstack/ai-event-client
@tanstack/ai-fal
@tanstack/ai-gemini
@tanstack/ai-grok
@tanstack/ai-groq
@tanstack/ai-isolate-cloudflare
@tanstack/ai-isolate-node
@tanstack/ai-isolate-quickjs
@tanstack/ai-ollama
@tanstack/ai-openai
@tanstack/ai-openrouter
@tanstack/ai-preact
@tanstack/ai-react
@tanstack/ai-react-ui
@tanstack/ai-solid
@tanstack/ai-solid-ui
@tanstack/ai-svelte
@tanstack/ai-vue
@tanstack/ai-vue-ui
@tanstack/preact-ai-devtools
@tanstack/react-ai-devtools
@tanstack/solid-ai-devtools
commit: |
There was a problem hiding this comment.
🧹 Nitpick comments (3)
scripts/fetch-openrouter-models.ts (1)
196-204: Consider adding a fetch timeout for CI resilience.The
fetchcall has no timeout configured. If the OpenRouter API is slow or unresponsive, this could cause the CI job to hang until the runner's global timeout kicks in.♻️ Optional: Add AbortController timeout
async function main() { console.log(`Fetching models from ${API_URL}...`) - const response = await fetch(API_URL) + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), 30_000) + + let response: Response + try { + response = await fetch(API_URL, { signal: controller.signal }) + } finally { + clearTimeout(timeoutId) + } if (!response.ok) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/fetch-openrouter-models.ts` around lines 196 - 204, The fetch in main() against API_URL lacks a timeout which can hang CI; wrap the fetch call with an AbortController-based timeout: create an AbortController, start a timer (e.g., setTimeout) that calls controller.abort() after a short configurable duration, pass controller.signal to fetch(API_URL, { signal }), and clear the timer once the response is received; handle the abort by catching the thrown error and rethrowing or logging a clear timeout-specific message so failure is deterministic in scripts/fetch-openrouter-models.ts..github/workflows/sync-models.yml (1)
48-68: Consider using heredoc for clearer changeset package building.The current pattern with embedded newlines in bash string concatenation works but is fragile and confuses static analysis tools. Using a heredoc or cleaner string building would improve readability.
♻️ Optional: Cleaner package list building
PACKAGES="" if echo "$CHANGED_FILES" | grep -q "packages/typescript/ai-openai/"; then - PACKAGES="${PACKAGES}'@tanstack/ai-openai': patch -" + PACKAGES="$PACKAGES'@tanstack/ai-openai': patch"$'\n' fi if echo "$CHANGED_FILES" | grep -q "packages/typescript/ai-anthropic/"; then - PACKAGES="${PACKAGES}'@tanstack/ai-anthropic': patch -" + PACKAGES="$PACKAGES'@tanstack/ai-anthropic': patch"$'\n' fi if echo "$CHANGED_FILES" | grep -q "packages/typescript/ai-gemini/"; then - PACKAGES="${PACKAGES}'@tanstack/ai-gemini': patch -" + PACKAGES="$PACKAGES'@tanstack/ai-gemini': patch"$'\n' fi if echo "$CHANGED_FILES" | grep -q "packages/typescript/ai-grok/"; then - PACKAGES="${PACKAGES}'@tanstack/ai-grok': patch -" + PACKAGES="$PACKAGES'@tanstack/ai-grok': patch"$'\n' fi if echo "$CHANGED_FILES" | grep -q "packages/typescript/ai-openrouter/"; then - PACKAGES="${PACKAGES}'@tanstack/ai-openrouter': patch -" + PACKAGES="$PACKAGES'@tanstack/ai-openrouter': patch"$'\n' fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/sync-models.yml around lines 48 - 68, Replace the fragile newline-embedded concatenation of PACKAGES with a clearer heredoc or join-based build: detect changed paths using the existing CHANGED_FILES and the same grep checks (the blocks referencing "packages/typescript/ai-openai/", "ai-anthropic/", "ai-gemini/", "ai-grok/", "ai-openrouter/"), but instead of appending strings with embedded newlines to PACKAGES, collect matching package keys into an array or heredoc buffer and then write the final PACKAGES content in one step; update references to PACKAGES and preserve the same package entries ('@tanstack/ai-openai': patch, etc.) so downstream consumers see the exact same output format.scripts/sync-provider-models.ts (1)
527-531: Minor: RedundantisNonChatModelcheck.The
isNonChatModel(strippedId)check at line 530 is redundant because line 480 already filters out non-chat models before they're added tonewModels. However, this is defensive and doesn't cause issues.♻️ Optional: Remove redundant check
// Filter to chat-eligible models: must output text and not be a non-chat model family const chatModels = filteredModels.filter( - ({ model, strippedId }) => - outputsText(model) && !isNonChatModel(strippedId), + ({ model }) => outputsText(model), )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/sync-provider-models.ts` around lines 527 - 531, The chatModels filter currently checks both outputsText(model) and !isNonChatModel(strippedId) but isNonChatModel is redundant because non-chat models were already excluded when building newModels; remove the !isNonChatModel(strippedId) clause from the filteredModels.filter call that assigns chatModels so the predicate only calls outputsText(model), keeping the variable name chatModels and functions outputsText and isNonChatModel unchanged elsewhere.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In @.github/workflows/sync-models.yml:
- Around line 48-68: Replace the fragile newline-embedded concatenation of
PACKAGES with a clearer heredoc or join-based build: detect changed paths using
the existing CHANGED_FILES and the same grep checks (the blocks referencing
"packages/typescript/ai-openai/", "ai-anthropic/", "ai-gemini/", "ai-grok/",
"ai-openrouter/"), but instead of appending strings with embedded newlines to
PACKAGES, collect matching package keys into an array or heredoc buffer and then
write the final PACKAGES content in one step; update references to PACKAGES and
preserve the same package entries ('@tanstack/ai-openai': patch, etc.) so
downstream consumers see the exact same output format.
In `@scripts/fetch-openrouter-models.ts`:
- Around line 196-204: The fetch in main() against API_URL lacks a timeout which
can hang CI; wrap the fetch call with an AbortController-based timeout: create
an AbortController, start a timer (e.g., setTimeout) that calls
controller.abort() after a short configurable duration, pass controller.signal
to fetch(API_URL, { signal }), and clear the timer once the response is
received; handle the abort by catching the thrown error and rethrowing or
logging a clear timeout-specific message so failure is deterministic in
scripts/fetch-openrouter-models.ts.
In `@scripts/sync-provider-models.ts`:
- Around line 527-531: The chatModels filter currently checks both
outputsText(model) and !isNonChatModel(strippedId) but isNonChatModel is
redundant because non-chat models were already excluded when building newModels;
remove the !isNonChatModel(strippedId) clause from the filteredModels.filter
call that assigns chatModels so the predicate only calls outputsText(model),
keeping the variable name chatModels and functions outputsText and
isNonChatModel unchanged elsewhere.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9c22cc15-5bbe-43ff-b598-e861b437115a
📒 Files selected for processing (6)
.github/workflows/sync-models.ymlpackage.jsonpackages/typescript/ai-openrouter/src/model-meta.tsscripts/fetch-openrouter-models.tsscripts/openrouter.models.tsscripts/sync-provider-models.ts
|
Caution Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted. Error details |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
scripts/sync-provider-models.ts (3)
14-16: ESLint reports unsorted imports.The
dirnameimport should come beforeresolvealphabetically. This is auto-fixable witheslint --fix.-import { resolve, dirname } from 'node:path' +import { dirname, resolve } from 'node:path'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/sync-provider-models.ts` around lines 14 - 16, ESLint flagged the import ordering: in the import line "import { resolve, dirname } from 'node:path'" the named imports are not alphabetized; reorder the named imports so "dirname" appears before "resolve" (i.e., import { dirname, resolve } from 'node:path') or run eslint --fix to apply the auto-fix; update the import statement in the module where fileURLToPath, dirname, and resolve are imported so named imports are alphabetized.
530-534: RedundantisNonChatModelcheck.Non-chat models are already filtered out at line 483. This second check at line 533 is unnecessary since
filteredModelsonly contains models that passed the earlier filter.// Filter to chat-eligible models: must output text and not be a non-chat model family const chatModels = filteredModels.filter( - ({ model, strippedId }) => - outputsText(model) && !isNonChatModel(strippedId), + ({ model }) => outputsText(model), )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/sync-provider-models.ts` around lines 530 - 534, The filter for chat-eligible models redundantly checks isNonChatModel on items coming from filteredModels (which were already filtered earlier), so remove the unnecessary predicate: change the chatModels assignment to only use outputsText (i.e., filter filteredModels by outputsText(model)) and delete the isNonChatModel check; locate the chatModels variable and the call site that uses outputsText and isNonChatModel in that block to update accordingly.
386-404: Static analysis ReDoS warning is a false positive here.The
arrayNamevariable is sourced from hardcodedPROVIDER_MAPconfig values (e.g.,'OPENAI_CHAT_MODELS'), not user input. This internal CI script has no attack surface for ReDoS. The non-greedy*?quantifier also limits backtracking.Consider adding a brief comment noting the input is trusted:
function addToArray( content: string, arrayName: string, entries: Array<string>, arrayRef: string, ): string { - // Match the array declaration: export const ARRAY_NAME = [...] as const + // Match the array declaration: export const ARRAY_NAME = [...] as const + // Note: arrayName comes from hardcoded PROVIDER_MAP, not user input // Uses [\s\S]*? (non-greedy) instead of [^\]]* to handle ] inside comments🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/sync-provider-models.ts` around lines 386 - 404, The ReDoS static analysis false positive can be addressed by documenting that arrayName values come from the internal PROVIDER_MAP and are not user-controlled; add a brief inline comment above the RegExp construction (the `pattern`/`new RegExp(...)` usage in the `addEntriesToArray`-style logic) stating that `arrayName` is trusted/hardcoded (e.g., from PROVIDER_MAP like 'OPENAI_CHAT_MODELS') and that the non-greedy `*?` limits backtracking, so the RegExp is safe in this CI-only script; keep the RegExp logic (`pattern`, `match`, and the replacement function) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/typescript/ai-openrouter/src/model-meta.ts`:
- Around line 16996-16997: Remove the Lyria model entries
(GOOGLE_LYRIA_3_CLIP_PREVIEW.id and GOOGLE_LYRIA_3_PRO_PREVIEW.id) from the chat
model registry so OpenRouterTextModels (derived from OPENROUTER_CHAT_MODELS)
does not accept Lyria; locate the entries in model-meta.ts and delete them or
move them to a non-chat registry, ensuring openRouterText() /
OpenRouterTextModels and the OpenRouterTextAdapter remain text-only and Lyria
stays excluded from sync.
---
Nitpick comments:
In `@scripts/sync-provider-models.ts`:
- Around line 14-16: ESLint flagged the import ordering: in the import line
"import { resolve, dirname } from 'node:path'" the named imports are not
alphabetized; reorder the named imports so "dirname" appears before "resolve"
(i.e., import { dirname, resolve } from 'node:path') or run eslint --fix to
apply the auto-fix; update the import statement in the module where
fileURLToPath, dirname, and resolve are imported so named imports are
alphabetized.
- Around line 530-534: The filter for chat-eligible models redundantly checks
isNonChatModel on items coming from filteredModels (which were already filtered
earlier), so remove the unnecessary predicate: change the chatModels assignment
to only use outputsText (i.e., filter filteredModels by outputsText(model)) and
delete the isNonChatModel check; locate the chatModels variable and the call
site that uses outputsText and isNonChatModel in that block to update
accordingly.
- Around line 386-404: The ReDoS static analysis false positive can be addressed
by documenting that arrayName values come from the internal PROVIDER_MAP and are
not user-controlled; add a brief inline comment above the RegExp construction
(the `pattern`/`new RegExp(...)` usage in the `addEntriesToArray`-style logic)
stating that `arrayName` is trusted/hardcoded (e.g., from PROVIDER_MAP like
'OPENAI_CHAT_MODELS') and that the non-greedy `*?` limits backtracking, so the
RegExp is safe in this CI-only script; keep the RegExp logic (`pattern`,
`match`, and the replacement function) unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 10423ba2-0dd6-4d3b-b989-33260ba295b3
📒 Files selected for processing (3)
packages/typescript/ai-openrouter/src/model-meta.tsscripts/openrouter.models.tsscripts/sync-provider-models.ts
- Exclude non-chat model families (lyria, veo, imagen, sora, dall-e, tts) from OPENROUTER_CHAT_MODELS in the convert script - Add 30s fetch timeout via AbortSignal.timeout - Fix import ordering (dirname before resolve) - Remove redundant isNonChatModel check in chatModels filter
|
All CodeRabbit feedback addressed in 4c00a3e:
|
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
packages/typescript/ai-openrouter/src/model-meta.ts (1)
16996-16997:⚠️ Potential issue | 🔴 CriticalKeep Lyria out of
OPENROUTER_CHAT_MODELS.Line 16996 and Line 16997 make
openRouterText()/ summarize accept Lyria becauseOpenRouterTextModelsis derived from this array inpackages/typescript/ai-openrouter/src/adapters/text.ts:46-54,74-89andpackages/typescript/ai-openrouter/src/adapters/summarize.ts:10-13. These models are not chat-safe here—their metadata advertisesoutput: ['text', 'audio']—so this breaks the text-adapter contract and conflicts with the PR goal of excluding Lyria from the synced chat registry.Suggested change
GOOGLE_GEMMA_4_26B_A4B_IT.id, GOOGLE_GEMMA_4_26B_A4B_IT_FREE.id, GOOGLE_GEMMA_4_31B_IT.id, GOOGLE_GEMMA_4_31B_IT_FREE.id, - GOOGLE_LYRIA_3_CLIP_PREVIEW.id, - GOOGLE_LYRIA_3_PRO_PREVIEW.id, GRYPHE_MYTHOMAX_L2_13B.id,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/typescript/ai-openrouter/src/model-meta.ts` around lines 16996 - 16997, The OPENROUTER_CHAT_MODELS array currently includes GOOGLE_LYRIA_3_CLIP_PREVIEW.id and GOOGLE_LYRIA_3_PRO_PREVIEW.id which causes OpenRouterTextModels (and thus openRouterText() / summarize adapters) to accept Lyria models; remove those two ids from OPENROUTER_CHAT_MODELS so Lyria is excluded, preserving the text-adapter contract (see OpenRouterTextModels in adapters/text.ts and summarize.ts) and ensuring Lyria's output types are not treated as chat models.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/sync-provider-models.ts`:
- Around line 391-394: The code currently logs a warning when the expected array
anchor (variable match) is missing and continues, which leads to writing files
and incrementing totals; change this to fail fast: when match is falsy for the
given arrayName, throw an Error (or return a rejected Promise) instead of
console.warn and return content, ensuring the caller does not proceed to write
the file or increment counters; update both occurrences that check match (the
block using arrayName/ content/ match around the current console.warn and the
similar check near lines 422-425) and ensure the caller code that writes files
and updates totals only runs when no error is thrown (or handle the rejection to
abort the sync).
- Line 15: The named imports in the top-level import statement use the wrong
alphabetical order; change the import from "{ resolve, dirname }" to "{ dirname,
resolve }" in the import that currently references resolve and dirname so the
symbols dirname and resolve are sorted alphabetically and satisfy the
sort-imports lint rule.
- Around line 472-493: The code only checks existingIds/existingConstNames but
can still add duplicate entries among the current run; update the loop that
builds newModels (using providerModels, strippedId, constName, normalizeId,
stripPrefix, toConstName, isNonChatModel) to also track seenNormalizedIds and
seenConstNames for this run and skip adding a model if normalizeId(strippedId)
or constName has already been added to newModels; ensure you add each accepted
model's normalized id and constName to those local sets before pushing to
newModels so dot-vs-dash aliases or other intra-run duplicates are prevented.
---
Duplicate comments:
In `@packages/typescript/ai-openrouter/src/model-meta.ts`:
- Around line 16996-16997: The OPENROUTER_CHAT_MODELS array currently includes
GOOGLE_LYRIA_3_CLIP_PREVIEW.id and GOOGLE_LYRIA_3_PRO_PREVIEW.id which causes
OpenRouterTextModels (and thus openRouterText() / summarize adapters) to accept
Lyria models; remove those two ids from OPENROUTER_CHAT_MODELS so Lyria is
excluded, preserving the text-adapter contract (see OpenRouterTextModels in
adapters/text.ts and summarize.ts) and ensuring Lyria's output types are not
treated as chat models.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: cea313f3-f3cb-49f0-a0d8-0ce30eecb3c1
📒 Files selected for processing (4)
.github/workflows/sync-models.ymlpackages/typescript/ai-openrouter/src/model-meta.tsscripts/openrouter.models.tsscripts/sync-provider-models.ts
✅ Files skipped from review due to trivial changes (1)
- .github/workflows/sync-models.yml
| for (const model of providerModels) { | ||
| const strippedId = stripPrefix(prefix, model.id) | ||
| const constName = toConstName(prefix, model.id) | ||
|
|
||
| // Skip models with ':' variants (e.g., 'anthropic/claude-3.7-sonnet:thinking') | ||
| // These are routing variants, not separate models | ||
| if (strippedId.includes(':')) { | ||
| continue | ||
| } | ||
|
|
||
| // Skip non-chat model families (audio/music/video/image generation) | ||
| if (isNonChatModel(strippedId)) { | ||
| continue | ||
| } | ||
|
|
||
| // Normalize for comparison to handle dots-vs-dashes naming differences | ||
| if ( | ||
| !existingIds.has(normalizeId(strippedId)) && | ||
| !existingConstNames.has(constName) | ||
| ) { | ||
| newModels.push({ model, constName, strippedId }) | ||
| } |
There was a problem hiding this comment.
Prevent duplicate aliases within the same run before pushing newModels.
existingIds/existingConstNames only guard against what is already on disk. They do not prevent duplicates among new models in the current run (e.g., dot-vs-dash aliases), which can produce duplicate const names and broken generated files. See Line [489]-Line [492].
Suggested fix
- const newModels: Array<{
+ const newModels: Array<{
model: OpenRouterModel
constName: string
strippedId: string
}> = []
+ const seenIds = new Set(existingIds)
+ const seenConstNames = new Set(existingConstNames)
for (const model of providerModels) {
const strippedId = stripPrefix(prefix, model.id)
const constName = toConstName(prefix, model.id)
+ const normalizedId = normalizeId(strippedId)
// Skip models with ':' variants (e.g., 'anthropic/claude-3.7-sonnet:thinking')
// These are routing variants, not separate models
if (strippedId.includes(':')) {
continue
@@
- if (
- !existingIds.has(normalizeId(strippedId)) &&
- !existingConstNames.has(constName)
- ) {
+ if (!seenIds.has(normalizedId) && !seenConstNames.has(constName)) {
newModels.push({ model, constName, strippedId })
+ seenIds.add(normalizedId)
+ seenConstNames.add(constName)
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for (const model of providerModels) { | |
| const strippedId = stripPrefix(prefix, model.id) | |
| const constName = toConstName(prefix, model.id) | |
| // Skip models with ':' variants (e.g., 'anthropic/claude-3.7-sonnet:thinking') | |
| // These are routing variants, not separate models | |
| if (strippedId.includes(':')) { | |
| continue | |
| } | |
| // Skip non-chat model families (audio/music/video/image generation) | |
| if (isNonChatModel(strippedId)) { | |
| continue | |
| } | |
| // Normalize for comparison to handle dots-vs-dashes naming differences | |
| if ( | |
| !existingIds.has(normalizeId(strippedId)) && | |
| !existingConstNames.has(constName) | |
| ) { | |
| newModels.push({ model, constName, strippedId }) | |
| } | |
| const newModels: Array<{ | |
| model: OpenRouterModel | |
| constName: string | |
| strippedId: string | |
| }> = [] | |
| const seenIds = new Set(existingIds) | |
| const seenConstNames = new Set(existingConstNames) | |
| for (const model of providerModels) { | |
| const strippedId = stripPrefix(prefix, model.id) | |
| const constName = toConstName(prefix, model.id) | |
| const normalizedId = normalizeId(strippedId) | |
| // Skip models with ':' variants (e.g., 'anthropic/claude-3.7-sonnet:thinking') | |
| // These are routing variants, not separate models | |
| if (strippedId.includes(':')) { | |
| continue | |
| } | |
| // Skip non-chat model families (audio/music/video/image generation) | |
| if (isNonChatModel(strippedId)) { | |
| continue | |
| } | |
| // Normalize for comparison to handle dots-vs-dashes naming differences | |
| if (!seenIds.has(normalizedId) && !seenConstNames.has(constName)) { | |
| newModels.push({ model, constName, strippedId }) | |
| seenIds.add(normalizedId) | |
| seenConstNames.add(constName) | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/sync-provider-models.ts` around lines 472 - 493, The code only checks
existingIds/existingConstNames but can still add duplicate entries among the
current run; update the loop that builds newModels (using providerModels,
strippedId, constName, normalizeId, stripPrefix, toConstName, isNonChatModel) to
also track seenNormalizedIds and seenConstNames for this run and skip adding a
model if normalizeId(strippedId) or constName has already been added to
newModels; ensure you add each accepted model's normalized id and constName to
those local sets before pushing to newModels so dot-vs-dash aliases or other
intra-run duplicates are prevented.
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (2)
scripts/sync-provider-models.ts (2)
466-492:⚠️ Potential issue | 🟠 MajorDeduplicate aliases within the current run too.
This only guards against ids and constant names that already exist on disk. Two OpenRouter aliases that normalize to the same id during the same sync can still both be pushed into
newModels, which will generate duplicate constants or duplicate array/type-map entries.Suggested fix
const newModels: Array<{ model: OpenRouterModel constName: string strippedId: string }> = [] + const seenIds = new Set(existingIds) + const seenConstNames = new Set(existingConstNames) for (const model of providerModels) { const strippedId = stripPrefix(prefix, model.id) const constName = toConstName(prefix, model.id) + const normalizedId = normalizeId(strippedId) @@ - if ( - !existingIds.has(normalizeId(strippedId)) && - !existingConstNames.has(constName) - ) { + if (!seenIds.has(normalizedId) && !seenConstNames.has(constName)) { newModels.push({ model, constName, strippedId }) + seenIds.add(normalizedId) + seenConstNames.add(constName) } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/sync-provider-models.ts` around lines 466 - 492, The loop that builds newModels can still add duplicate entries from providerModels that normalize to the same id or constName within a single run; update the logic in the providerModels loop (the code that computes strippedId, constName and calls normalizeId) to maintain two local in-memory sets (e.g., seenNormalizedIds and seenConstNames) in addition to existingIds/existingConstNames, check these sets before pushing to newModels, and add the normalized id and constName to those sets immediately when you push to newModels to prevent duplicates in newModels and subsequent generation steps.
391-394:⚠️ Potential issue | 🟠 MajorAbort when an expected array/type-map anchor is missing.
Returning the original content here still lets the script write the file and report models as added, so a partial sync looks successful. Throw instead and let the run fail fast.
Suggested fix
function addToArray( content: string, arrayName: string, entries: Array<string>, arrayRef: string, ): string { @@ const match = pattern.exec(content) if (!match) { - console.warn(` Warning: Could not find array '${arrayName}' in file`) - return content + throw new Error(`Could not find array '${arrayName}' in file`) } @@ } function addToTypeMap( content: string, typeName: string, entries: Array<string>, ): string { @@ const match = pattern.exec(content) if (!match) { - console.warn(` Warning: Could not find type map '${typeName}' in file`) - return content + throw new Error(`Could not find type map '${typeName}' in file`) } @@ }Also applies to: 422-425
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/sync-provider-models.ts` around lines 391 - 394, The code currently returns the original content when an expected anchor array is missing (checks using match and arrayName) which allows a partial sync to proceed; change these early returns to throw a descriptive Error (including arrayName and any anchor identifier) so the run fails fast instead of writing a partial file; apply the same change to the similar block that checks the type-map anchor (the match/typeMapName check around lines 422-425) so both missing-anchor cases raise errors rather than returning content.
🧹 Nitpick comments (1)
scripts/convert-openrouter-models.ts (1)
143-147: Align non-chat filtering withsync-provider-models.ts.This block matches any occurrence of
lyria|veo|imagen|sora|dall-e|ttsanywhere in the full OpenRouter id, whilescripts/sync-provider-models.tstreats these as stripped-id prefixes. That can makeOPENROUTER_CHAT_MODELSdisagree with the sync pipeline and can falsely exclude ids that merely contain one of those substrings. Reusing the same shared prefix helper here would keep both generators in sync.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/convert-openrouter-models.ts` around lines 143 - 147, The non-chat detection currently uses nonChatFamilies and isNonChat to check model.id.includes(f), which can falsely match substrings; change it to use the same stripped-id prefix helper used in scripts/sync-provider-models.ts (import or extract the helper) and test the stripped id's prefix with startsWith against the same set (e.g., call the shared helper like getStrippedId(model.id) or use isNonChatPrefix(strippedId)) so that the check in the block that references outputModalities and isNonChat uses prefix matching instead of includes and stays consistent with the sync pipeline.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/fetch-openrouter-models.ts`:
- Line 13: The named imports from 'node:path' are unsorted; update the import to
alphabetically order the symbols so it reads "import { dirname, resolve } from
'node:path'"; change the import that currently lists "resolve, dirname" to
"dirname, resolve" (referencing the existing symbols resolve and dirname).
- Around line 55-63: The runtime validation is insufficient: update isValidModel
to use a Zod schema that mirrors ApiModel's nested shape (including
architecture.modality, architecture.input_modalities as array, pricing.prompt,
top_provider, etc.) and use Zod.safeParse when handling the fetched response
(instead of the current TypeScript cast around json.data at the fetch step) to
validate the entire API response before filtering; then have serializeModel rely
on the validated shape (or return/skip invalid entries) so accesses like
serializeModel -> arch.modality, arch.input_modalities.map, and pricing.prompt
cannot crash at runtime. Ensure you add a top-level response schema for the
fetch result and handle validation failures (log or skip) rather than assuming
json.data is correct.
In `@scripts/sync-provider-models.ts`:
- Around line 39-50: The current code reuses provider-level templates
referenceSupportsBody and referenceProviderOptionsEntry for every model which
incorrectly applies the same features/tools/output capabilities to all models;
change the logic that builds model entries (where models are stamped using
referenceSupportsBody and referenceProviderOptionsEntry) to compute those
strings per-model using the OpenRouter model capability payload (extract input
modalities, features, tools, output capabilities from each model) and generate a
model-specific supports block and provider-options entry for that model (fall
back to a conservative base type only if the model payload is missing required
fields); update the places that check hasBothNameAndId and
providerOptionsIsMappedType to use the per-model providerOptions entry so each
ModelMeta reflects the model's actual capabilities rather than a provider-wide
template.
---
Duplicate comments:
In `@scripts/sync-provider-models.ts`:
- Around line 466-492: The loop that builds newModels can still add duplicate
entries from providerModels that normalize to the same id or constName within a
single run; update the logic in the providerModels loop (the code that computes
strippedId, constName and calls normalizeId) to maintain two local in-memory
sets (e.g., seenNormalizedIds and seenConstNames) in addition to
existingIds/existingConstNames, check these sets before pushing to newModels,
and add the normalized id and constName to those sets immediately when you push
to newModels to prevent duplicates in newModels and subsequent generation steps.
- Around line 391-394: The code currently returns the original content when an
expected anchor array is missing (checks using match and arrayName) which allows
a partial sync to proceed; change these early returns to throw a descriptive
Error (including arrayName and any anchor identifier) so the run fails fast
instead of writing a partial file; apply the same change to the similar block
that checks the type-map anchor (the match/typeMapName check around lines
422-425) so both missing-anchor cases raise errors rather than returning
content.
---
Nitpick comments:
In `@scripts/convert-openrouter-models.ts`:
- Around line 143-147: The non-chat detection currently uses nonChatFamilies and
isNonChat to check model.id.includes(f), which can falsely match substrings;
change it to use the same stripped-id prefix helper used in
scripts/sync-provider-models.ts (import or extract the helper) and test the
stripped id's prefix with startsWith against the same set (e.g., call the shared
helper like getStrippedId(model.id) or use isNonChatPrefix(strippedId)) so that
the check in the block that references outputModalities and isNonChat uses
prefix matching instead of includes and stays consistent with the sync pipeline.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8d7734c3-86df-4f6a-b17f-595427133e1d
📒 Files selected for processing (4)
packages/typescript/ai-openrouter/src/model-meta.tsscripts/convert-openrouter-models.tsscripts/fetch-openrouter-models.tsscripts/sync-provider-models.ts
| */ | ||
|
|
||
| import { writeFile } from 'node:fs/promises' | ||
| import { resolve, dirname } from 'node:path' |
There was a problem hiding this comment.
Sort the named node:path imports.
This still trips the sort-imports rule.
Minimal fix
-import { resolve, dirname } from 'node:path'
+import { dirname, resolve } from 'node:path'🧰 Tools
🪛 ESLint
[error] 13-13: Member 'dirname' of the import declaration should be sorted alphabetically.
(sort-imports)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/fetch-openrouter-models.ts` at line 13, The named imports from
'node:path' are unsorted; update the import to alphabetically order the symbols
so it reads "import { dirname, resolve } from 'node:path'"; change the import
that currently lists "resolve, dirname" to "dirname, resolve" (referencing the
existing symbols resolve and dirname).
| function isValidModel(model: ApiModel): boolean { | ||
| return ( | ||
| typeof model.id === 'string' && | ||
| typeof model.name === 'string' && | ||
| typeof model.context_length === 'number' && | ||
| model.architecture != null && | ||
| model.pricing != null && | ||
| model.top_provider != null | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n scripts/fetch-openrouter-models.ts | head -80Repository: TanStack/ai
Length of output: 2931
🏁 Script executed:
cat -n scripts/fetch-openrouter-models.ts | sed -n '80,220p'Repository: TanStack/ai
Length of output: 5415
Validate nested API payload fields with runtime schema validation.
isValidModel() only checks that architecture, pricing, and top_provider are non-null, but doesn't validate their internal structure. The code can crash at runtime in serializeModel() when accessing nested properties:
- Line 96:
arch.modality— crashes ifmodalityis missing - Line 98:
arch.input_modalities.map()— crashes if the array is missing - Line 118:
pricing.prompt— crashes if missing
Similarly, line 208 casts the response with TypeScript's as operator, which provides no runtime validation; json.data could be null or contain malformed models.
Use Zod to validate the entire API response schema at lines 208-209 before filtering with isValidModel(), ensuring both the response structure and nested fields conform to the expected shape.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/fetch-openrouter-models.ts` around lines 55 - 63, The runtime
validation is insufficient: update isValidModel to use a Zod schema that mirrors
ApiModel's nested shape (including architecture.modality,
architecture.input_modalities as array, pricing.prompt, top_provider, etc.) and
use Zod.safeParse when handling the fetched response (instead of the current
TypeScript cast around json.data at the fetch step) to validate the entire API
response before filtering; then have serializeModel rely on the validated shape
(or return/skip invalid entries) so accesses like serializeModel ->
arch.modality, arch.input_modalities.map, and pricing.prompt cannot crash at
runtime. Ensure you add a top-level response schema for the fetch result and
handle validation failures (log or skip) rather than assuming json.data is
correct.
- generate:models is now the full pipeline (fetch + convert + sync) - regenerate:models is the old openrouter-only convert - Sync script now creates changeset file when models are added - CI workflow simplified (no longer creates changeset, just commits) - Restored missing workflow file lost during merge
There was a problem hiding this comment.
♻️ Duplicate comments (3)
scripts/sync-provider-models.ts (3)
472-500:⚠️ Potential issue | 🟠 MajorDeduplicate within the current run too.
The current check only compares against what is already on disk. If two OpenRouter entries in the same fetch normalize to the same id or const name, both can still be pushed into
newModelsand generate duplicate declarations.Suggested fix
const newModels: Array<{ model: OpenRouterModel constName: string strippedId: string }> = [] + const seenIds = new Set(existingIds) + const seenConstNames = new Set(existingConstNames) for (const model of providerModels) { const strippedId = stripPrefix(prefix, model.id) const constName = toConstName(prefix, model.id) + const normalizedId = normalizeId(strippedId) @@ - if ( - !existingIds.has(normalizeId(strippedId)) && - !existingConstNames.has(constName) - ) { + if (!seenIds.has(normalizedId) && !seenConstNames.has(constName)) { newModels.push({ model, constName, strippedId }) + seenIds.add(normalizedId) + seenConstNames.add(constName) } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/sync-provider-models.ts` around lines 472 - 500, The loop that populates newModels can still produce duplicates from the same fetch because it only checks existingIds/existingConstNames; update the logic in the providerModels loop (around newModels, strippedId, constName, normalizeId, toConstName) to also track in-run seen sets (e.g., seenNormalizedIds and seenConstNames) and skip pushing if normalizeId(strippedId) or constName is already in those seen sets; add the normalized id and constName to the seen sets when you push to newModels so duplicate entries from the same run are deduplicated.
397-400:⚠️ Potential issue | 🟠 MajorFail the sync when an insertion anchor is missing.
Returning the original content here lets the script keep writing files and incrementing totals after a partial update. That can turn a broken sync into a false success in CI.
Suggested fix
const match = pattern.exec(content) if (!match) { - console.warn(` Warning: Could not find array '${arrayName}' in file`) - return content + throw new Error(`Could not find array '${arrayName}' in file`) } @@ const match = pattern.exec(content) if (!match) { - console.warn(` Warning: Could not find type map '${typeName}' in file`) - return content + throw new Error(`Could not find type map '${typeName}' in file`) }Also applies to: 428-430
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/sync-provider-models.ts` around lines 397 - 400, The current block silently warns and returns original content when the regex anchor (match) for arrayName is missing, allowing the sync to continue and produce false-success results; instead, make the operation fail hard by throwing an error (or calling process.exit(1)) with a clear message including arrayName so the CI job fails; update the code paths that use the match variable (the block referencing match and arrayName at the shown location and the similar block around lines 428-430) to throw a descriptive Error rather than returning content or just logging a warning.
41-48:⚠️ Potential issue | 🟠 MajorGenerate capabilities and provider options per model, not per provider.
referenceSupportsBody,referenceSatisfies, andreferenceProviderOptionsEntryare stamped onto every new model for a provider. That overstates model-specific features/tools/options in the generatedmodel-meta.tsfiles and breaks the repo’s per-model narrowing guarantees.Based on learnings: packages/typescript/*/src/model-meta.ts should maintain model metadata files that define provider options and capabilities per model for per-model type safety.
Also applies to: 321-340, 553-558
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/sync-provider-models.ts` around lines 41 - 48, The current template stamps provider-level values (referenceSupportsBody, referenceSatisfies, referenceProviderOptionsEntry) onto every model; change the generation so these three symbols are computed and injected per model during the model iteration rather than once per provider: in the model generation loop in sync-provider-models.ts, derive referenceSupportsBody, referenceSatisfies and referenceProviderOptionsEntry from the specific model's metadata/inputModalities and use those per-model values when writing the model-meta.ts content (instead of reusing a single provider-level template), and remove or rename any provider-scoped variables so provider templates only contain provider-wide pieces.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@scripts/sync-provider-models.ts`:
- Around line 472-500: The loop that populates newModels can still produce
duplicates from the same fetch because it only checks
existingIds/existingConstNames; update the logic in the providerModels loop
(around newModels, strippedId, constName, normalizeId, toConstName) to also
track in-run seen sets (e.g., seenNormalizedIds and seenConstNames) and skip
pushing if normalizeId(strippedId) or constName is already in those seen sets;
add the normalized id and constName to the seen sets when you push to newModels
so duplicate entries from the same run are deduplicated.
- Around line 397-400: The current block silently warns and returns original
content when the regex anchor (match) for arrayName is missing, allowing the
sync to continue and produce false-success results; instead, make the operation
fail hard by throwing an error (or calling process.exit(1)) with a clear message
including arrayName so the CI job fails; update the code paths that use the
match variable (the block referencing match and arrayName at the shown location
and the similar block around lines 428-430) to throw a descriptive Error rather
than returning content or just logging a warning.
- Around line 41-48: The current template stamps provider-level values
(referenceSupportsBody, referenceSatisfies, referenceProviderOptionsEntry) onto
every model; change the generation so these three symbols are computed and
injected per model during the model iteration rather than once per provider: in
the model generation loop in sync-provider-models.ts, derive
referenceSupportsBody, referenceSatisfies and referenceProviderOptionsEntry from
the specific model's metadata/inputModalities and use those per-model values
when writing the model-meta.ts content (instead of reusing a single
provider-level template), and remove or rename any provider-scoped variables so
provider templates only contain provider-wide pieces.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: bfccc6e2-ddf9-481f-9b2b-a0f9ef368da8
📒 Files selected for processing (8)
.changeset/sync-models-1775817980896.md.github/workflows/sync-models.ymlpackage.jsonpackages/typescript/ai-anthropic/src/model-meta.tspackages/typescript/ai-gemini/src/model-meta.tspackages/typescript/ai-grok/src/model-meta.tspackages/typescript/ai-openai/src/model-meta.tsscripts/sync-provider-models.ts
✅ Files skipped from review due to trivial changes (2)
- .changeset/sync-models-1775817980896.md
- .github/workflows/sync-models.yml
🚧 Files skipped from review as they are similar to previous changes (1)
- package.json
- Skip deprecated/legacy model families per provider: - OpenAI: gpt-3.5-*, gpt-4-*, gpt-4o*, gpt-oss-*, chatgpt-* - Google: gemma-* (open-source, not Gemini API models) - Skip models created >30 days before last sync run - Track last run timestamp in scripts/.sync-models-last-run - Re-sync with filters: 5 models added (was 47 without filters)
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
scripts/sync-provider-models.ts (1)
170-178: Modality order depends on OpenRouter source data.The function only prepends
'text'if it's absent—it doesn't normalize the order when'text'is already present elsewhere in the array. This results in generated models having['image', 'text']vs['text', 'image']depending on OpenRouter's data ordering. Functionally correct, but causes minor inconsistency with manually-defined models.♻️ Optional: normalize order for consistency
function mapInputModalities(modalities: Array<string>): Array<InputModality> { const mapped = modalities .map((m) => MODALITY_MAP[m.toLowerCase()]) .filter((m): m is InputModality => m !== undefined) - // Ensure at least 'text' is present - if (!mapped.includes('text')) { - mapped.unshift('text') - } - return mapped + // Ensure 'text' is always first for consistency + const withoutText = mapped.filter((m) => m !== 'text') + return ['text', ...withoutText] }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/sync-provider-models.ts` around lines 170 - 178, The mapInputModalities function currently only prepends 'text' when missing, causing inconsistent ordering if 'text' appears later; update mapInputModalities (working with the mapped array produced from MODALITY_MAP) to ensure 'text' is always at index 0—if mapped contains 'text' but not at position 0, remove that entry and unshift 'text' to the front (preserve the existing .map/.filter type narrowing logic and return type InputModality[]).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/sync-provider-models.ts`:
- Around line 663-667: The code currently always writes config.metaFile and
increments totalAdded/changedPackages even when addToArray or addToTypeMap made
no changes; modify the logic in the block around writeFile/config.metaFile so
you first keep the original file content (e.g., origContent), run
addToArray/addToTypeMap producing new content, then compare origContent ===
content (or a success flag returned by those functions); only call writeFile,
console.log, increment totalAdded by filteredModels.length, and add to
changedPackages when the content actually changed; if unchanged, emit the
existing warning/skip behavior and do not alter totalAdded or changedPackages.
---
Nitpick comments:
In `@scripts/sync-provider-models.ts`:
- Around line 170-178: The mapInputModalities function currently only prepends
'text' when missing, causing inconsistent ordering if 'text' appears later;
update mapInputModalities (working with the mapped array produced from
MODALITY_MAP) to ensure 'text' is always at index 0—if mapped contains 'text'
but not at position 0, remove that entry and unshift 'text' to the front
(preserve the existing .map/.filter type narrowing logic and return type
InputModality[]).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1aa192d8-613f-4fc7-89e8-d6e03f99e78f
📒 Files selected for processing (6)
.changeset/sync-models-1775821757387.md.github/workflows/sync-models.ymlpackages/typescript/ai-grok/src/model-meta.tspackages/typescript/ai-openai/src/model-meta.tsscripts/.sync-models-last-runscripts/sync-provider-models.ts
✅ Files skipped from review due to trivial changes (3)
- scripts/.sync-models-last-run
- .changeset/sync-models-1775821757387.md
- .github/workflows/sync-models.yml
| // Write the modified file | ||
| await writeFile(config.metaFile, content, 'utf-8') | ||
| console.log(` Wrote updated file: ${config.metaFile}`) | ||
| totalAdded += filteredModels.length | ||
| changedPackages.add(config.packageName) |
There was a problem hiding this comment.
File is written regardless of whether array/type anchors were found.
If addToArray or addToTypeMap fail to find their targets (returning unchanged content with a warning), the file is still written and totalAdded is incremented. This relates to the fail-fast concern from past reviews—partial modifications silently succeed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/sync-provider-models.ts` around lines 663 - 667, The code currently
always writes config.metaFile and increments totalAdded/changedPackages even
when addToArray or addToTypeMap made no changes; modify the logic in the block
around writeFile/config.metaFile so you first keep the original file content
(e.g., origContent), run addToArray/addToTypeMap producing new content, then
compare origContent === content (or a success flag returned by those functions);
only call writeFile, console.log, increment totalAdded by filteredModels.length,
and add to changedPackages when the content actually changed; if unchanged, emit
the existing warning/skip behavior and do not alter totalAdded or
changedPackages.
Summary
scripts/fetch-openrouter-models.tsto fetch fresh model data from the OpenRouter APIscripts/sync-provider-models.tsto sync new models into native provider packages (ai-openai, ai-anthropic, ai-gemini, ai-grok).github/workflows/sync-models.ymlfor daily CI that fetches, converts, syncs, creates a changeset, and opens a PRgenerate:models:fetchandgenerate:models:syncscripts to root package.jsonNew models use actual input modalities from OpenRouter and assume full provider capabilities for features/tools/endpoints. Non-chat model families (lyria, veo, imagen, sora, dall-e, tts) are excluded. The daily CI creates a single rolling PR on the
automated/sync-modelsbranch.Test Plan
pnpm generate:models:sync)workflow_dispatchSummary by CodeRabbit
Release Notes
New Features
Updates