Skip to content

Commit 6f8873b

Browse files
committed
Added compare script and fixed key name issue in fetch models
Fixes #310
1 parent 2bbda65 commit 6f8873b

2 files changed

Lines changed: 187 additions & 3 deletions

File tree

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/**
2+
* Compare openrouter.models.ts between the current branch and main.
3+
* Outputs a markdown summary of added/removed/updated models suitable for a PR description.
4+
*
5+
* Usage: npx tsx scripts/compare-openrouter-models.ts [--updated]
6+
*/
7+
8+
import { execSync } from 'node:child_process'
9+
import { readFileSync } from 'node:fs'
10+
import { resolve } from 'node:path'
11+
12+
interface ModelSnapshot {
13+
id: string
14+
name: string
15+
context_length: string
16+
pricing_prompt: string
17+
pricing_completion: string
18+
modality: string
19+
}
20+
21+
/**
22+
* Extract model snapshots from source using regex.
23+
* Captures the key fields that reviewers care about for detecting updates.
24+
*/
25+
function extractModels(source: string): Map<string, ModelSnapshot> {
26+
const models = new Map<string, ModelSnapshot>()
27+
28+
// Split into model blocks (each starts with ` {` and ends with ` },` or ` }\n]`)
29+
const blocks = source.split(/\n {2}\{/)
30+
31+
for (const block of blocks) {
32+
const id = block.match(/id:\s*'([^']+)'/)?.[1]
33+
if (!id) continue
34+
35+
const name = block.match(/name:\s*'([^']+)'/)?.[1] ?? ''
36+
const context_length =
37+
block.match(/^\s*context_length:\s*(.+),?\s*$/m)?.[1] ?? ''
38+
const pricing_prompt = block.match(/prompt:\s*'([^']+)'/)?.[1] ?? ''
39+
const pricing_completion = block.match(/completion:\s*'([^']+)'/)?.[1] ?? ''
40+
const modality = block.match(/modality:\s*'([^']+)'/)?.[1] ?? ''
41+
42+
models.set(id, {
43+
id,
44+
name,
45+
context_length,
46+
pricing_prompt,
47+
pricing_completion,
48+
modality,
49+
})
50+
}
51+
52+
return models
53+
}
54+
55+
function describeChanges(
56+
oldModel: ModelSnapshot,
57+
newModel: ModelSnapshot,
58+
): Array<string> {
59+
const changes: Array<string> = []
60+
61+
if (oldModel.name !== newModel.name) {
62+
changes.push(`name: "${oldModel.name}" → "${newModel.name}"`)
63+
}
64+
if (oldModel.context_length !== newModel.context_length) {
65+
changes.push(
66+
`context_length: ${oldModel.context_length}${newModel.context_length}`,
67+
)
68+
}
69+
if (oldModel.pricing_prompt !== newModel.pricing_prompt) {
70+
changes.push(
71+
`prompt price: ${oldModel.pricing_prompt}${newModel.pricing_prompt}`,
72+
)
73+
}
74+
if (oldModel.pricing_completion !== newModel.pricing_completion) {
75+
changes.push(
76+
`completion price: ${oldModel.pricing_completion}${newModel.pricing_completion}`,
77+
)
78+
}
79+
if (oldModel.modality !== newModel.modality) {
80+
changes.push(`modality: "${oldModel.modality}" → "${newModel.modality}"`)
81+
}
82+
83+
return changes
84+
}
85+
86+
const showUpdated = process.argv.includes('--updated')
87+
88+
const modelsPath = 'scripts/openrouter.models.ts'
89+
90+
// Current branch version
91+
const currentSource = readFileSync(
92+
resolve(import.meta.dirname, '..', modelsPath),
93+
'utf-8',
94+
)
95+
96+
// Main branch version — hardcoded git command, no user input (safe from injection)
97+
let mainSource: string
98+
try {
99+
mainSource = execSync(`git show main:${modelsPath}`, {
100+
encoding: 'utf-8',
101+
})
102+
} catch {
103+
console.error('Could not read main branch version. Are you in a git repo?')
104+
process.exit(1)
105+
}
106+
107+
const currentModels = extractModels(currentSource)
108+
const mainModels = extractModels(mainSource)
109+
110+
const currentIds = [...currentModels.keys()]
111+
const mainIds = [...mainModels.keys()]
112+
const currentSet = new Set(currentIds)
113+
const mainSet = new Set(mainIds)
114+
115+
const added = currentIds.filter((id) => !mainSet.has(id)).sort()
116+
const removed = mainIds.filter((id) => !currentSet.has(id)).sort()
117+
118+
// Find updated models (present in both, but with changed fields)
119+
const updated: Array<{ id: string; changes: Array<string> }> = []
120+
if (showUpdated) {
121+
for (const id of currentIds) {
122+
if (!mainSet.has(id)) continue
123+
const changes = describeChanges(mainModels.get(id)!, currentModels.get(id)!)
124+
if (changes.length > 0) {
125+
updated.push({ id, changes })
126+
}
127+
}
128+
updated.sort((a, b) => a.id.localeCompare(b.id))
129+
}
130+
131+
// Output markdown
132+
const lines: Array<string> = []
133+
134+
lines.push('## OpenRouter Models Update Summary')
135+
lines.push('')
136+
lines.push(
137+
`**Model count**: ${mainIds.length} (main) → ${currentIds.length} (this branch)`,
138+
)
139+
lines.push('')
140+
141+
if (added.length > 0) {
142+
lines.push(`### Added models (${added.length})`)
143+
lines.push('')
144+
for (const id of added) {
145+
lines.push(`- \`${id}\``)
146+
}
147+
lines.push('')
148+
}
149+
150+
if (removed.length > 0) {
151+
lines.push(`### Removed models (${removed.length})`)
152+
lines.push('')
153+
for (const id of removed) {
154+
lines.push(`- \`${id}\``)
155+
}
156+
lines.push('')
157+
}
158+
159+
if (updated.length > 0) {
160+
lines.push(`### Updated models (${updated.length})`)
161+
lines.push('')
162+
for (const { id, changes } of updated) {
163+
lines.push(`- \`${id}\`: ${changes.join(', ')}`)
164+
}
165+
lines.push('')
166+
}
167+
168+
if (added.length === 0 && removed.length === 0 && updated.length === 0) {
169+
lines.push('No changes detected.')
170+
lines.push('')
171+
}
172+
173+
console.log(lines.join('\n'))

scripts/fetch-openrouter-models.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ const OUTPUT_PATH = new URL('./openrouter.models.ts', import.meta.url)
55

66
const ARRAY_START = 'export const models: Array<OpenRouterModel> = ['
77

8+
const VALID_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/
9+
10+
function isRecord(v: unknown): v is Record<string, unknown> {
11+
return typeof v === 'object' && v !== null && !Array.isArray(v)
12+
}
13+
14+
function serializeKey(k: string): string {
15+
return VALID_IDENTIFIER.test(k) ? k : JSON.stringify(k)
16+
}
17+
818
function serializeValue(value: unknown, indent: number): string {
919
const pad = ' '.repeat(indent)
1020
const childPad = ' '.repeat(indent + 1)
@@ -33,11 +43,12 @@ function serializeValue(value: unknown, indent: number): string {
3343
)
3444
return `[\n${items.join('\n')}\n${pad}]`
3545
}
36-
if (typeof value === 'object') {
37-
const entries = Object.entries(value as Record<string, unknown>)
46+
if (isRecord(value)) {
47+
const entries = Object.entries(value)
3848
if (entries.length === 0) return '{}'
3949
const lines = entries.map(
40-
([k, v]) => `${childPad}${k}: ${serializeValue(v, indent + 1)},`,
50+
([k, v]) =>
51+
`${childPad}${serializeKey(k)}: ${serializeValue(v, indent + 1)},`,
4152
)
4253
return `{\n${lines.join('\n')}\n${pad}}`
4354
}

0 commit comments

Comments
 (0)