fix: update logic for generate config

This commit is contained in:
2026-09-04 17:09:06 +07:00
parent dbea31f4a5
commit 7f3caa3fae
18 changed files with 1155 additions and 22 deletions
+158
View File
@@ -0,0 +1,158 @@
import { describe, it, expect } from 'bun:test'
import {
normalizeModelId,
lookupCatalog,
resolveModelLimits,
FALLBACK_CONTEXT,
FALLBACK_OUTPUT,
} from './modelCatalog.ts'
import { MODEL_CATALOG } from './modelCatalog.generated.ts'
describe('modelCatalog', () => {
describe('normalizeModelId', () => {
it('normalizes provider prefixed IDs', () => {
expect(normalizeModelId('cc/claude-sonnet-4.5')).toBe('claude-sonnet-4-5')
expect(normalizeModelId('anthropic/claude-opus-4-7')).toBe('claude-opus-4-7')
})
it('normalizes bare IDs', () => {
expect(normalizeModelId('claude-sonnet-4.5')).toBe('claude-sonnet-4-5')
})
it('normalizes multi-tier IDs', () => {
expect(normalizeModelId('openrouter/anthropic/claude-sonnet-4.5')).toBe('anthropic/claude-sonnet-4-5')
})
it('handles uppercase characters', () => {
expect(normalizeModelId('CC/Claude-Sonnet-4.5')).toBe('claude-sonnet-4-5')
})
})
describe('lookupCatalog - verified IDs', () => {
const verifiedMatches: [string, string][] = [
['cc/claude-sonnet-4.5', 'anthropic/claude-sonnet-4-5'],
['kr/claude-sonnet-4.5', 'anthropic/claude-sonnet-4-5'],
['cc/claude-opus-4-5-20251101', 'anthropic/claude-opus-4-5-20251101'],
['cx/gpt-5.6-sol', 'openai/gpt-5.6-sol'],
['cx/gpt-5.3-codex', 'openai/gpt-5.3-codex'],
['ag/gemini-3.1-pro-preview', 'google/gemini-3.1-pro-preview'],
['vertex/gemini-3-flash-preview', 'google/gemini-3-flash-preview'],
['openai/gpt-4o', 'openai/gpt-4o'],
['openai/o3-mini', 'openai/o3-mini'],
['deepseek/deepseek-r1', 'deepseek/deepseek-r1'],
['cc/claude-opus-4-7', 'anthropic/claude-opus-4-7'],
]
for (const [queryId, expectedCatalogKey] of verifiedMatches) {
it(`resolves ${queryId} -> ${expectedCatalogKey}`, () => {
const hit = lookupCatalog(queryId)
expect(hit).not.toBeNull()
expect(hit!.key).toBe(expectedCatalogKey)
})
}
it('returns null on unknown models', () => {
expect(lookupCatalog('foo/definitely-not-real')).toBeNull()
})
it('matches date suffix aliases (tier 4)', () => {
// cc/claude-sonnet-4-6-20260101 does not exist in catalog, but anthropic/claude-sonnet-4-6 does
const hit = lookupCatalog('cc/claude-sonnet-4-6-20260101')
expect(hit).not.toBeNull()
expect(hit!.key).toBe('anthropic/claude-sonnet-4-6')
})
})
describe('catalog integrity & fuzz invariant', () => {
const catalogKeys = Object.keys(MODEL_CATALOG)
it('contains usable catalog entries', () => {
expect(catalogKeys.length).toBeGreaterThanOrEqual(300)
})
it('validates every entry in MODEL_CATALOG adheres to invariants', () => {
for (const key of catalogKeys) {
const entry = MODEL_CATALOG[key]
expect(entry.c).toBeGreaterThan(0)
if (entry.o !== undefined) {
expect(entry.o).toBeGreaterThan(0)
}
if (entry.e !== undefined) {
expect(entry.e.length).toBeGreaterThan(0)
}
const limits = resolveModelLimits({ id: key })
expect(limits.maxInputTokens).toBeGreaterThanOrEqual(1)
expect(limits.maxOutputTokens).toBeGreaterThanOrEqual(1)
expect(limits.maxInputTokens + limits.maxOutputTokens).toBeLessThanOrEqual(limits.contextWindow)
expect(limits.source).toBe('models.dev')
}
})
})
describe('resolveModelLimits specifics', () => {
it('preserves non-degenerate limits where output > context / 2 (zhipuai/glm-4.7)', () => {
const hit = lookupCatalog('zhipuai/glm-4.7')
if (hit) {
const limits = resolveModelLimits({ id: 'zhipuai/glm-4.7' })
expect(limits.maxOutputTokens).toBe(131072)
expect(limits.maxInputTokens).toBe(73728)
expect(limits.maxInputTokens + limits.maxOutputTokens).toBe(204800)
}
})
it('preserves non-degenerate limits (openai/gpt-5-pro)', () => {
const hit = lookupCatalog('openai/gpt-5-pro')
if (hit) {
const limits = resolveModelLimits({ id: 'openai/gpt-5-pro' })
expect(limits.maxOutputTokens).toBe(272000)
expect(limits.maxInputTokens).toBe(128000)
expect(limits.maxInputTokens + limits.maxOutputTokens).toBe(400000)
}
})
it('synthesizes degenerate output limit when declared output >= context (xai/grok-4.6)', () => {
const hit = lookupCatalog('xai/grok-4.6')
if (hit && hit.entry.o === hit.entry.c) {
const limits = resolveModelLimits({ id: 'xai/grok-4.6' })
expect(limits.maxOutputTokens).toBe(125000)
expect(limits.maxInputTokens).toBe(375000)
expect(limits.maxInputTokens + limits.maxOutputTokens).toBe(500000)
}
})
it('follows precedence: models.dev wins over gateway arguments', () => {
const limits = resolveModelLimits({
id: 'cc/claude-sonnet-4.5',
contextWindow: 999999,
maxOutputTokens: 1111,
})
expect(limits.source).toBe('models.dev')
expect(limits.contextWindow).toBe(200000)
expect(limits.maxOutputTokens).toBe(64000)
expect(limits.maxInputTokens).toBe(136000)
})
it('uses gateway limits when uncataloged', () => {
const limits = resolveModelLimits({
id: 'private/custom-model',
contextWindow: 64000,
maxOutputTokens: 8192,
})
expect(limits.source).toBe('gateway')
expect(limits.contextWindow).toBe(64000)
expect(limits.maxOutputTokens).toBe(8192)
expect(limits.maxInputTokens).toBe(55808)
})
it('uses defaults when uncataloged and no gateway limits provided', () => {
const limits = resolveModelLimits({
id: 'private/custom-model',
})
expect(limits.source).toBe('default')
expect(limits.contextWindow).toBe(FALLBACK_CONTEXT)
expect(limits.maxOutputTokens).toBe(FALLBACK_OUTPUT)
expect(limits.maxInputTokens).toBe(FALLBACK_CONTEXT - FALLBACK_OUTPUT)
})
})
})