Repository navigation
feat: enrich auto-router model capabilities from lowest common denominator #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -335,6 +335,73 @@ export function isComboModel(model: OmniRouteModel): boolean { | |
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Identify the OmniRoute "auto" zero-config router model. | ||
| * OmniRoute exposes this as a model id of literally `auto` (optionally | ||
| * prefixed, e.g. `omniroute/auto`). Unlike user-defined combos, it is not | ||
| * listed in `/api/combos`, so it needs its own capability calculation. | ||
| */ | ||
| export function isAutoModel(model: OmniRouteModel): boolean { | ||
| const { modelKey } = splitModelId(model.id); | ||
| return modelKey.toLowerCase() === 'auto'; | ||
| } | ||
|
|
||
| /** | ||
| * Enrich the "auto" model (if present in the fetched model list) with | ||
| * capabilities computed as the lowest common denominator across every | ||
| * other known model. This mirrors how user-defined combo capabilities are | ||
| * calculated, and ensures the "auto" router's advertised context window / | ||
| * capabilities automatically track the OmniRoute catalog as it grows or | ||
| * shrinks, without requiring any hardcoded overrides. | ||
| * | ||
| * This should be called after models.dev + combo enrichment, so that the | ||
| * "other models" pool already has resolved capabilities where possible. | ||
| */ | ||
| export function enrichAutoModel(models: OmniRouteModel[]): OmniRouteModel[] { | ||
| const autoIndex = models.findIndex(isAutoModel); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔥 The Roast: 🩹 The Fix: Pick a single canonical auto model (prefer bare 📏 Severity: suggestion Reply with |
||
| if (autoIndex === -1) return models; | ||
|
|
||
| const autoModel = models[autoIndex]; | ||
|
|
||
| // If OmniRoute or models.dev already provided full capability data, don't override it. | ||
| if (autoModel.contextWindow !== undefined && autoModel.maxTokens !== undefined) { | ||
| return models; | ||
| } | ||
|
|
||
| const others = models.filter((model, index) => index !== autoIndex && !isAutoModel(model)); | ||
| if (others.length === 0) { | ||
| return models; | ||
| } | ||
|
|
||
| const withContext = others.filter((m): m is OmniRouteModel & { contextWindow: number } => m.contextWindow !== undefined); | ||
| const withMaxTokens = others.filter((m): m is OmniRouteModel & { maxTokens: number } => m.maxTokens !== undefined); | ||
|
|
||
| const computedContextWindow = | ||
| autoModel.contextWindow ?? (withContext.length > 0 ? Math.min(...withContext.map((m) => m.contextWindow)) : undefined); | ||
| const computedMaxTokens = | ||
| autoModel.maxTokens ?? (withMaxTokens.length > 0 ? Math.min(...withMaxTokens.map((m) => m.maxTokens)) : undefined); | ||
|
|
||
| debug( | ||
| `Calculated capabilities for auto-router model "${sanitizeForLog(autoModel.id)}" from ${others.length} known models: context=${computedContextWindow ?? 'N/A'}, maxTokens=${computedMaxTokens ?? 'N/A'}`, | ||
| ); | ||
|
|
||
| const updated: OmniRouteModel = { | ||
| ...autoModel, | ||
| ...(computedContextWindow !== undefined ? { contextWindow: computedContextWindow } : {}), | ||
| ...(computedMaxTokens !== undefined ? { maxTokens: computedMaxTokens } : {}), | ||
| supportsVision: autoModel.supportsVision ?? others.every((m) => m.supportsVision === true), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔥 The Roast: The "lowest common denominator" AND logic treats 🩹 The Fix: Ignore models whose capability is unknown when computing the AND, e.g. 📏 Severity: warning Reply with |
||
| supportsTools: autoModel.supportsTools ?? others.every((m) => m.supportsTools === true), | ||
| supportsStreaming: autoModel.supportsStreaming ?? others.every((m) => m.supportsStreaming === true), | ||
| supportsTemperature: autoModel.supportsTemperature ?? others.every((m) => m.supportsTemperature === true), | ||
| supportsReasoning: autoModel.supportsReasoning ?? others.some((m) => m.supportsReasoning === true), | ||
| supportsAttachment: autoModel.supportsAttachment ?? others.every((m) => m.supportsAttachment === true), | ||
| }; | ||
|
|
||
| const result = [...models]; | ||
| result[autoIndex] = updated; | ||
| return result; | ||
| } | ||
|
|
||
| /** | ||
| * Enrich models with combo-specific capabilities | ||
| * This should be called after models.dev enrichment | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| import { test } from 'node:test'; | ||
| import assert from 'node:assert/strict'; | ||
|
|
||
| import { enrichAutoModel, isAutoModel } from '../dist/src/omniroute-combos.js'; | ||
|
|
||
| test('isAutoModel matches bare "auto" and prefixed ids', () => { | ||
| assert.equal(isAutoModel({ id: 'auto' }), true); | ||
| assert.equal(isAutoModel({ id: 'omniroute/auto' }), true); | ||
| assert.equal(isAutoModel({ id: 'auto-pilot' }), false); | ||
| assert.equal(isAutoModel({ id: 'gpt-4o' }), false); | ||
| }); | ||
|
|
||
| test('enrichAutoModel computes lowest-common capabilities from other models', () => { | ||
| const models = [ | ||
| { id: 'auto', name: 'Auto' }, | ||
| { | ||
| id: 'openai/gpt-4o', | ||
| name: 'GPT-4o', | ||
| contextWindow: 128000, | ||
| maxTokens: 16384, | ||
| supportsVision: true, | ||
| supportsTools: true, | ||
| supportsStreaming: true, | ||
| supportsTemperature: true, | ||
| supportsReasoning: false, | ||
| supportsAttachment: true, | ||
| }, | ||
| { | ||
| id: 'anthropic/claude-3-5-sonnet', | ||
| name: 'Claude 3.5 Sonnet', | ||
| contextWindow: 200000, | ||
| maxTokens: 8192, | ||
| supportsVision: true, | ||
| supportsTools: true, | ||
| supportsStreaming: true, | ||
| supportsTemperature: true, | ||
| supportsReasoning: true, | ||
| supportsAttachment: true, | ||
| }, | ||
| ]; | ||
|
|
||
| const result = enrichAutoModel(models); | ||
| const auto = result.find((m) => m.id === 'auto'); | ||
|
|
||
| assert.equal(auto.contextWindow, 128000, 'context window should be the minimum across models'); | ||
| assert.equal(auto.maxTokens, 8192, 'max tokens should be the minimum across models'); | ||
| assert.equal(auto.supportsVision, true); | ||
| assert.equal(auto.supportsTools, true); | ||
| assert.equal(auto.supportsReasoning, true, 'reasoning should be true if any model supports it'); | ||
| }); | ||
|
|
||
| test('enrichAutoModel is a no-op when no auto model is present', () => { | ||
| const models = [ | ||
| { id: 'openai/gpt-4o', name: 'GPT-4o', contextWindow: 128000 }, | ||
| ]; | ||
| const result = enrichAutoModel(models); | ||
| assert.deepEqual(result, models); | ||
| }); | ||
|
|
||
| test('enrichAutoModel does not override explicit capabilities already provided', () => { | ||
| const models = [ | ||
| { id: 'auto', name: 'Auto', contextWindow: 999999, maxTokens: 999 }, | ||
| { id: 'openai/gpt-4o', name: 'GPT-4o', contextWindow: 128000, maxTokens: 16384 }, | ||
| ]; | ||
| const result = enrichAutoModel(models); | ||
| const auto = result.find((m) => m.id === 'auto'); | ||
| assert.equal(auto.contextWindow, 999999); | ||
| assert.equal(auto.maxTokens, 999); | ||
| }); | ||
|
|
||
| test('enrichAutoModel recalculates when only one capability field is missing', () => { | ||
| const models = [ | ||
| { id: 'auto', name: 'Auto', maxTokens: 4096 }, | ||
| { id: 'openai/gpt-4o', name: 'GPT-4o', contextWindow: 128000, maxTokens: 16384 }, | ||
| { id: 'anthropic/claude-3-5-sonnet', name: 'Claude', contextWindow: 64000, maxTokens: 8192 }, | ||
| ]; | ||
| const result = enrichAutoModel(models); | ||
| const auto = result.find((m) => m.id === 'auto'); | ||
| assert.equal(auto.contextWindow, 64000, 'should fill in missing context window from minimum'); | ||
| assert.equal(auto.maxTokens, 4096, 'should keep pre-existing maxTokens untouched'); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔥 The Roast: The new Auto-Router sectiongot a little too eager and stamped
### API Modetwice. Two identical headings now sit back-to-back like a copy-paste that forgot to press delete. Anyone following the TOC will think they've fallen into a documentation time loop.🩹 The Fix: Remove the duplicate
### API Modeheading this PR added (keep the original one that introduces the API Modes content).📏 Severity: nitpick
Reply with
@kilocode-bot fix itto have Kilo Code address this issue.