Merge remote-tracking branch 'origin/main' into feat/vscode-routed-model-usage

This commit is contained in:
Josh Lambert
2026-06-28 11:31:41 +02:00
36 changed files with 493 additions and 29 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Open the model picker expanded by default, keep the preview pinned while hovering, separate Kilo Auto models into their own marked group with summary tooltips, and show backend-provided Auto Efficient model choices.
+59 -13
View File
@@ -22,31 +22,73 @@ env:
jobs:
# kilocode_change start
changes:
name: detect general unit test changes
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
pull-requests: read
outputs:
settings: ${{ steps.matrix.outputs.settings }}
steps:
- name: Checkout repository
if: github.event_name != 'workflow_dispatch'
uses: actions/checkout@v6
- name: Detect general unit test changes
if: github.event_name != 'workflow_dispatch'
id: filter
uses: Kilo-Org/paths-filter@668c092af3649c4b664c54e4b704aa46782f6f7c # v3
with:
predicate-quantifier: every
filters: |
general:
- '**'
- '!.changeset/**'
- '!packages/kilo-jetbrains/**'
- '!packages/kilo-vscode/**'
- '!packages/kilo-docs/**'
- '!packages/extensions/zed/**'
- '!specs/**'
- '!perf/**'
- name: Configure unit matrix
id: matrix
env:
GENERAL: ${{ github.event_name != 'pull_request' || steps.filter.outputs.general == 'true' }}
run: |
if [ "$GENERAL" = "true" ]; then
echo 'settings=[{"name":"linux","host":"blacksmith-4vcpu-ubuntu-2404","run":true},{"name":"macos","host":"macos-15","run":true},{"name":"windows","host":"blacksmith-4vcpu-windows-2025","run":true}]' >> "$GITHUB_OUTPUT"
exit 0
fi
echo 'settings=[{"name":"linux","host":"blacksmith-4vcpu-ubuntu-2404","run":false}]' >> "$GITHUB_OUTPUT"
unit:
name: unit (${{ matrix.settings.name }})
needs: changes
strategy:
fail-fast: false
matrix:
settings:
- name: linux
host: blacksmith-4vcpu-ubuntu-2404 # kilocode_change
- name: macos
host: macos-15 # kilocode_change
- name: windows
host: blacksmith-4vcpu-windows-2025 # kilocode_change
settings: ${{ fromJSON(needs.changes.outputs.settings) }}
runs-on: ${{ matrix.settings.host }}
timeout-minutes: 45 # kilocode_change
defaults:
run:
shell: bash
steps:
- name: Skip unchanged general unit tests
if: ${{ !matrix.settings.run }}
run: echo "Only isolated product, documentation, or metadata files changed; general unit tests are unchanged."
- name: Checkout repository
if: matrix.settings.run
uses: actions/checkout@v6 # kilocode_change
with:
token: ${{ secrets.GITHUB_TOKEN }}
# kilocode_change start
- name: Setup Node
if: matrix.settings.run
id: setup-node
continue-on-error: ${{ runner.os == 'Windows' }}
uses: actions/setup-node@v6 # kilocode_change
@@ -54,18 +96,19 @@ jobs:
node-version: "24"
- name: Retry Setup Node on Windows
if: runner.os == 'Windows' && steps.setup-node.outcome == 'failure'
if: matrix.settings.run && runner.os == 'Windows' && steps.setup-node.outcome == 'failure'
uses: actions/setup-node@v6
with:
node-version: "24"
# kilocode_change end
- name: Setup Bun
if: matrix.settings.run
uses: ./.github/actions/setup-bun
# kilocode_change start
- name: Setup Zig for Linux sandbox helper
if: runner.os == 'Linux'
if: matrix.settings.run && runner.os == 'Linux'
run: |
curl --fail --location --retry 3 \
https://ziglang.org/download/0.14.0/zig-linux-x86_64-0.14.0.tar.xz \
@@ -75,17 +118,19 @@ jobs:
echo "$RUNNER_TEMP/zig-linux-x86_64-0.14.0" >> "$GITHUB_PATH"
- name: Build Linux sandbox helper
if: runner.os == 'Linux'
if: matrix.settings.run && runner.os == 'Linux'
run: |
bun packages/opencode/script/kilocode/bubblewrap.ts --arch x64 --output "$RUNNER_TEMP/bwrap"
echo "KILO_BWRAP_PATH=$RUNNER_TEMP/bwrap" >> "$GITHUB_ENV"
# kilocode_change end
- name: Configure git identity
if: matrix.settings.run
run: |
git config --global user.email "kilo-maintainer[bot]@users.noreply.github.com"
git config --global user.name "kilo-maintainer[bot]"
- name: Cache Turbo
if: matrix.settings.run
uses: actions/cache@v5 # kilocode_change
with:
path: node_modules/.cache/turbo
@@ -95,6 +140,7 @@ jobs:
turbo-${{ runner.os }}-
- name: Run unit tests
if: matrix.settings.run
run: bun turbo test:ci --filter='!@kilocode/kilo-jetbrains'
env:
KILO_EXPERIMENTAL_DISABLE_FILEWATCHER: ${{ runner.os == 'Windows' && 'true' || 'false' }}
@@ -102,13 +148,13 @@ jobs:
# kilocode_change start
- name: Run HttpApi exerciser gates
if: runner.os == 'Linux'
if: matrix.settings.run && runner.os == 'Linux'
working-directory: packages/opencode
run: bun run test:httpapi
# kilocode_change end
- name: Publish unit reports # kilocode_change
if: always()
if: always() && matrix.settings.run
uses: mikepenz/action-junit-report@bccf2e31636835cf0874589931c4116687171386 # v6.4.0
with:
report_paths: packages/*/.artifacts/unit/junit.xml
@@ -118,7 +164,7 @@ jobs:
fail_on_failure: false
- name: Upload unit artifacts
if: always()
if: always() && matrix.settings.run
uses: actions/upload-artifact@v7 # kilocode_change
with:
name: unit-${{ matrix.settings.name }}-${{ github.run_attempt }}
+7
View File
@@ -38,6 +38,12 @@ const openRouterModelSchema = z.object({
isFree: z.boolean().optional(),
mayTrainOnYourPrompts: z.boolean().optional(),
hasUserByokAvailable: z.boolean().optional(),
autoRouting: z
.object({
models: z.array(z.string()),
})
.optional()
.catch(undefined),
terminalBench: z
.object({
overallScore: z.number(),
@@ -194,6 +200,7 @@ function transformToModelDevFormat(model: OpenRouterModel): any {
isFree: model.isFree,
mayTrainOnYourPrompts: model.mayTrainOnYourPrompts,
hasUserByokAvailable: model.hasUserByokAvailable,
...(model.autoRouting && { autoRouting: model.autoRouting }),
...(model.terminalBench && { terminalBench: model.terminalBench }),
...(inputPrice !== undefined &&
outputPrice !== undefined && {
@@ -42,6 +42,25 @@ const VALID_BENCH_RESPONSE = JSON.stringify({
],
})
const VALID_AUTO_ROUTING_RESPONSE = JSON.stringify({
data: [
{
id: "kilo-auto/efficient",
name: "Kilo Auto Efficient",
context_length: 128000,
max_completion_tokens: 16384,
architecture: {
input_modalities: ["text"],
output_modalities: ["text"],
},
supported_parameters: ["tools", "temperature"],
autoRouting: {
models: ["google/gemini-2.5-flash", "anthropic/claude-sonnet-4.6"],
},
},
],
})
const INVALID_BENCH_RESPONSE = JSON.stringify({
data: [
{
@@ -174,6 +193,26 @@ test("preserves Terminal Bench metadata as a dedicated model field", async () =>
})
})
test("preserves Auto Efficient routing metadata as a dedicated model field", async () => {
const orig = globalThis.fetch
stubFetch(
async () =>
new Response(VALID_AUTO_ROUTING_RESPONSE, {
status: 200,
headers: { "content-type": "application/json" },
}),
)
const result = await fetchKiloModels({})
;(globalThis as any).fetch = orig
expect(result.error).toBeUndefined()
expect(result.models["kilo-auto/efficient"].autoRouting).toEqual({
models: ["google/gemini-2.5-flash", "anthropic/claude-sonnet-4.6"],
})
})
test("omits malformed Terminal Bench metadata without rejecting the catalog", async () => {
const orig = globalThis.fetch
stubFetch(
@@ -28,23 +28,56 @@ test("model selector exposes combobox relationships and active option movement",
await expect(combobox).toHaveAccessibleDescription("Choose the model used for code review tasks.")
await expect(alpha.locator("button")).toHaveCount(0)
await expect(page.getByRole("button", { name: "Add to favorites: Alpha" })).toBeVisible()
await expect(page.locator(".model-selector-group-label").nth(0)).toContainText("Auto Models")
await expect(page.locator(".model-selector-group-label").nth(1)).toContainText("Recommended")
const auto = page.getByRole("treeitem", { name: /Kilo Auto Efficient/ })
await expect(auto).toBeVisible()
await expect(auto.locator(".model-selector-auto-icon")).toHaveAttribute(
"aria-label",
"Routes each request to the cheapest model that gets the job done, based on continuously benchmarked accuracy and cost.",
)
await expect(page.getByRole("treeitem", { name: "Omega" })).toBeVisible()
await combobox.press("ArrowDown")
await expect(combobox).toBeFocused()
await expect(combobox).toHaveAttribute("aria-activedescendant", await bravo.getAttribute("id"))
const expand = page.getByRole("button", { name: "Expand" })
const controls = await expand.getAttribute("aria-controls")
const preview = page.locator(`[id="${controls}"]`)
await expect(expand).toHaveAttribute("aria-expanded", "false")
await expect(preview).toHaveAttribute("aria-hidden", "true")
await expect(preview.locator("button, a, [tabindex]")).toHaveCount(0)
await expand.click()
const collapse = page.getByRole("button", { name: "Collapse", exact: true })
await expect(collapse).toHaveAttribute("aria-controls", controls!)
const controls = await collapse.getAttribute("aria-controls")
const preview = page.locator(`[id="${controls}"]`)
await expect(collapse).toHaveAttribute("aria-expanded", "true")
await expect(preview).toHaveAttribute("aria-hidden", "false")
await expect(preview.getByRole("button", { name: "Add to favorites" })).toBeVisible()
const listBeforePreview = await tree.evaluate((el, id) => {
const node = document.getElementById(id!)
return !!node && !!(el.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_FOLLOWING)
}, controls)
const treeBox = await tree.boundingBox()
const previewBox = await preview.boundingBox()
expect(listBeforePreview).toBe(true)
expect(previewBox!.y).toBeLessThan(treeBox!.y)
await collapse.click()
const expand = page.getByRole("button", { name: "Expand" })
await expect(expand).toHaveAttribute("aria-controls", controls!)
await expect(expand).toHaveAttribute("aria-expanded", "false")
await expect(preview).toHaveAttribute("aria-hidden", "true")
await expect(preview.locator("button, a, [tabindex]")).toHaveCount(0)
})
test("auto efficient details show server description and model choices", async ({ page }) => {
await load(page, "shared--model-selector-accessible")
await page.getByRole("button", { name: "Review model: Alpha" }).click()
await page.getByRole("treeitem", { name: /Kilo Auto Efficient/ }).click()
const preview = page.locator(".model-selector-preview")
await expect(preview).toContainText(
"Routes each request to the cheapest model that gets the job done, based on continuously benchmarked accuracy and cost.",
)
await expect(preview).toContainText("Model choices")
await expect(preview).toContainText("google/gemini-2.5-flash")
await expect(preview).toContainText("anthropic/claude-sonnet-4.6")
await expect(preview).not.toContainText("openai/gpt-5.5")
})
test("typing a provider initial moves the active descendant to matching results", async ({ page }) => {
@@ -65,7 +98,7 @@ test("provider groups collapse, expand, and skip their model rows", async ({ pag
await page.getByRole("button", { name: "Review model: Alpha" }).click()
const combobox = page.getByRole("combobox", { name: "Review model: Alpha. Search models" })
const kilo = page.getByRole("treeitem", { name: "Kilo" })
const kilo = page.getByRole("treeitem", { name: "Kilo", exact: true })
const nvidia = page.getByRole("treeitem", { name: "NVIDIA" })
await combobox.press("ArrowDown")
@@ -120,7 +153,10 @@ test("expanded preview waits for explicit pointer selection", async ({ page }) =
await load(page, "shared--model-selector-accessible")
await page.getByRole("button", { name: "Review model: Alpha" }).click()
await page.getByRole("button", { name: "Expand" }).click()
await expect(page.locator(".model-selector-preview")).toContainText("Alpha")
await page.getByRole("treeitem", { name: "Bravo" }).hover()
await page.waitForTimeout(250)
await expect(page.locator(".model-selector-preview")).toContainText("Alpha")
await page.getByRole("treeitem", { name: "Bravo" }).click()
await expect(page.getByTestId("model-selector-value")).toHaveText("alpha")
@@ -10,6 +10,9 @@ import {
isDataCollectedModel,
hasByok,
isFree,
isAuto,
autoSummary,
autoChoices,
} from "../../webview-ui/src/components/shared/model-selector-utils"
const labels = { select: "Select model", noProviders: "No providers", notSet: "Not set" }
@@ -112,6 +115,60 @@ describe("isFree", () => {
})
})
describe("isAuto", () => {
it("matches only Kilo Auto model ids", () => {
expect(isAuto({ providerID: KILO_GATEWAY_ID, id: "kilo-auto/efficient" })).toBe(true)
expect(isAuto({ providerID: KILO_GATEWAY_ID, id: "auto-small" })).toBe(true)
expect(isAuto({ providerID: "anthropic", id: "kilo-auto/efficient" })).toBe(false)
expect(isAuto({ providerID: KILO_GATEWAY_ID, id: "anthropic/claude-sonnet" })).toBe(false)
})
})
describe("autoChoices", () => {
it("uses backend Auto Efficient routes and resolves names when available", () => {
expect(
autoChoices(
{
providerID: KILO_GATEWAY_ID,
id: "kilo-auto/efficient",
autoRouting: { models: ["provider/model", "missing/model"] },
},
[{ id: "provider/model", name: "Provider: Model" }],
),
).toEqual([
{ id: "provider/model", name: "Model" },
{ id: "missing/model", name: "missing/model" },
])
})
it("ignores missing routes and non-efficient Auto models", () => {
expect(autoChoices({ providerID: KILO_GATEWAY_ID, id: "kilo-auto/efficient" })).toEqual([])
expect(
autoChoices({
providerID: KILO_GATEWAY_ID,
id: "kilo-auto/frontier",
autoRouting: { models: ["provider/model"] },
}),
).toEqual([])
})
})
describe("autoSummary", () => {
it("uses the first description paragraph for compact tooltips", () => {
expect(
autoSummary({
options: {
description: "Routes through available models.\n\nLong details.",
},
}),
).toBe("Routes through available models.")
})
it("falls back when there is no description", () => {
expect(autoSummary({})).toBe("Routes requests automatically.")
})
})
describe("isDataCollectedModel", () => {
it("uses only explicit prompt training metadata", () => {
expect(isDataCollectedModel({ mayTrainOnYourPrompts: true })).toBe(true)
@@ -5,11 +5,20 @@ import { Markdown } from "@kilocode/kilo-ui/markdown"
import { Icon } from "@kilocode/kilo-ui/icon"
import { Tooltip } from "@kilocode/kilo-ui/tooltip"
import { useLanguage } from "../../context/language"
import { freeDataLabel, hasByok, isDataCollectedModel, sanitizeName } from "./model-selector-utils"
import {
autoChoices,
autoSummary,
freeDataLabel,
hasByok,
isAuto,
isDataCollectedModel,
sanitizeName,
} from "./model-selector-utils"
import { avgPrice, fmtAttemptCost, fmtCachedPrice, fmtPrice, fmtTerminalBenchScore } from "./model-preview-utils"
interface Props {
model: EnrichedModel | null
models?: readonly EnrichedModel[]
}
function fmtContext(n: number): string {
@@ -49,6 +58,9 @@ export const ModelPreview: Component<Props> = (props) => {
const avg = () => (cost() ? avgPrice(cost()!) : undefined)
const freeLabel = () => language.t("model.tag.free")
const dataLabel = () => freeDataLabel(language.t("model.tag.free"), language.t("model.tag.dataCollected"))
const autoLabel = () => autoSummary(model())
const choices = () => autoChoices(model(), props.models ?? [])
const choicesLabel = () => language.t("model.preview.group.autoEfficientChoices")
const ctx = () => model().limit?.context ?? model().contextLength
const caps = () => model().capabilities
const inputs = () => caps()?.input
@@ -91,8 +103,15 @@ export const ModelPreview: Component<Props> = (props) => {
})()}
</Show>
</div>
<Show when={model().isFree || hasByok(model()) || isDataCollectedModel(model())}>
<Show when={isAuto(model()) || model().isFree || hasByok(model()) || isDataCollectedModel(model())}>
<span class="model-preview-free-data">
<Show when={isAuto(model())}>
<Tooltip value={autoLabel()} placement="top">
<span class="model-preview-auto-icon" aria-label={autoLabel()}>
<Icon name="branch" size="small" />
</span>
</Tooltip>
</Show>
<Show when={model().isFree && !hasByok(model())}>
<span class="model-preview-badge model-preview-badge--free">{freeLabel()}</span>
</Show>
@@ -181,6 +200,22 @@ export const ModelPreview: Component<Props> = (props) => {
<Markdown text={model().options!.description!} />
</div>
</Show>
<Show when={choices().length > 0}>
<div class="model-preview-group">
<span class="model-preview-group-title">{choicesLabel()}</span>
<div class="model-preview-candidates" aria-label={choicesLabel()}>
<For each={choices()}>
{(item) => (
<span class="model-preview-candidate" title={item.id}>
<span class="model-preview-candidate-name">{item.name}</span>
<span class="model-preview-candidate-id">{item.id}</span>
</span>
)}
</For>
</div>
</div>
</Show>
</>
)
}}
@@ -39,7 +39,9 @@ import {
isFree,
isDataCollectedModel,
hasByok,
isAuto,
freeDataLabel,
autoSummary,
buildTriggerLabel,
sanitizeName,
} from "./model-selector-utils"
@@ -52,6 +54,7 @@ import { searchMatch } from "../../utils/search-match"
const CLEAR_KEY = "clear"
const FAVORITES_KEY = "favorites"
const AUTO_KEY = "auto"
const RECOMMENDED_KEY = "recommended"
function modelKey(providerID: string, modelID: string) {
@@ -148,7 +151,7 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
}
const [open, setOpen] = createSignal(false)
const [expanded, setExpanded] = createSignal(false)
const [expanded, setExpanded] = createSignal(true)
const [search, setSearch] = createSignal("")
const [debouncedSearch, setDebouncedSearch] = createSignal("")
const [selectedKey, setSelectedKey] = createSignal(CLEAR_KEY)
@@ -185,7 +188,7 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
function onMove(e: MouseEvent) {
if (!body) return
const delta = startY - e.clientY
const delta = e.clientY - startY
// Subtract fixed chrome (search wrapper + splitter) so the list always
// retains at least 80px, rather than the preview consuming that space.
const chrome = (searchWrapperRef?.offsetHeight ?? 0) + (splitterRef?.offsetHeight ?? 0)
@@ -260,10 +263,15 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
})
const groups = createMemo<ModelGroup[]>(() => {
const autos: EnrichedModel[] = []
const recommended: EnrichedModel[] = []
const map = new Map<string, EnrichedModel[]>()
for (const m of filtered()) {
if (isAuto(m)) {
autos.push(m)
continue
}
if (m.recommendedIndex !== undefined) {
recommended.push(m)
continue
@@ -273,6 +281,9 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
map.set(m.providerID, list)
}
autos.sort(
(a, b) => (a.recommendedIndex ?? Infinity) - (b.recommendedIndex ?? Infinity) || a.name.localeCompare(b.name),
)
recommended.sort((a, b) => (a.recommendedIndex ?? Infinity) - (b.recommendedIndex ?? Infinity))
const result: ModelGroup[] = []
@@ -291,6 +302,18 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
})
}
if (autos.length > 0) {
result.push({
key: AUTO_KEY,
label: language.t("model.group.auto"),
rows: autos.map((m) => ({
key: rowKey("model", m.providerID, m.id),
kind: "model",
model: m,
})),
})
}
if (recommended.length > 0) {
result.push({
key: RECOMMENDED_KEY,
@@ -675,6 +698,7 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
const describedBy = () => (props.description ? descriptionID : undefined)
const freeLabel = () => language.t("model.tag.free")
const dataLabel = () => freeDataLabel(language.t("model.tag.free"), language.t("model.tag.dataCollected"))
const autoLabel = (model: EnrichedModel) => autoSummary(model)
const activeCollectsData = () => {
const model = activeModel()
if (!model) return false
@@ -917,7 +941,6 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
}}
onMouseEnter={() => {
if (pointer()) setSelectedKey(row.key)
schedulePreview(row.key)
}}
>
<div class="model-selector-item-left">
@@ -937,6 +960,13 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
)
})()}
</span>
<Show when={isAuto(model)}>
<Tooltip value={autoLabel(model)} placement="top">
<span class="model-selector-auto-icon" aria-label={autoLabel(model)}>
<Icon name="branch" size="small" />
</span>
</Tooltip>
</Show>
<Show when={isFree(model) || hasByok(model) || isDataCollectedModel(model)}>
<span class="model-selector-free-data">
<Show when={isFree(model) && !hasByok(model)}>
@@ -1011,7 +1041,7 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
style={expanded() ? { height: `${previewHeight()}px` } : {}}
>
<Show when={expanded()}>
<ModelPreview model={previewModel() ?? activeModel() ?? null} />
<ModelPreview model={previewModel() ?? activeModel() ?? null} models={visibleModels()} />
</Show>
</div>
</div>
@@ -9,6 +9,40 @@ import {
export { KILO_GATEWAY_ID, PROVIDER_ORDER }
export const KILO_AUTO_SMALL_IDS = new Set(["kilo-auto/small", "auto-small"])
export const KILO_AUTO_EFFICIENT_ID = "kilo-auto/efficient"
const AUTO_FALLBACK = "Routes requests automatically."
interface Choice {
id: string
name: string
}
export function isAuto(model: Pick<EnrichedModel, "providerID" | "id">): boolean {
return (
model.providerID === KILO_GATEWAY_ID && (model.id.startsWith("kilo-auto/") || KILO_AUTO_SMALL_IDS.has(model.id))
)
}
export function isAutoEfficient(model: Pick<EnrichedModel, "providerID" | "id">): boolean {
return model.providerID === KILO_GATEWAY_ID && model.id === KILO_AUTO_EFFICIENT_ID
}
export function autoChoices(
model: Pick<EnrichedModel, "providerID" | "id" | "autoRouting">,
catalog: readonly Pick<EnrichedModel, "id" | "name">[] = [],
): readonly Choice[] {
if (!isAutoEfficient(model)) return []
const ids = model.autoRouting?.models
if (!ids?.length) return []
const names = new Map(catalog.map((item) => [item.id, stripSubProviderPrefix(sanitizeName(item.name))]))
return ids.map((id) => ({ id, name: names.get(id) ?? id }))
}
export function autoSummary(model: Pick<EnrichedModel, "options">): string {
const raw = model.options?.description?.split(/\n\s*\n/)[0]
if (!raw) return AUTO_FALLBACK
return raw.replace(/\s+/g, " ").trim() || AUTO_FALLBACK
}
export function isSmall(model: Pick<EnrichedModel, "providerID" | "id">): boolean {
return model.providerID === KILO_GATEWAY_ID && KILO_AUTO_SMALL_IDS.has(model.id)
+2
View File
@@ -181,6 +181,7 @@ export const dict = {
"model.tag.free": "مجاني",
"model.tag.dataCollected": "قد تُستخدم البيانات للتدريب",
"model.tag.latest": "الأحدث",
"model.group.auto": "النماذج التلقائية",
"model.group.recommended": "موصى به",
"model.group.favorites": "المفضلة",
"model.group.collapse": "طي {{group}}",
@@ -208,6 +209,7 @@ export const dict = {
"model.preview.label.average": "متوسط التكلفة التقديرية",
"model.preview.label.context": "السياق",
"model.preview.group.terminalBench": "Terminal Bench 2.0",
"model.preview.group.autoEfficientChoices": "خيارات النماذج",
"model.preview.label.completion": "الإكمال",
"model.preview.label.costAttempt": "التكلفة / المحاولة",
"model.preview.value.notSupported": "غير مدعوم",
+2
View File
@@ -182,6 +182,7 @@ export const dict = {
"model.tag.free": "Grátis",
"model.tag.dataCollected": "Os dados podem ser usados para treinamento",
"model.tag.latest": "Mais recente",
"model.group.auto": "Modelos automáticos",
"model.group.recommended": "Recomendado",
"model.group.favorites": "Favoritos",
"model.group.collapse": "Recolher {{group}}",
@@ -209,6 +210,7 @@ export const dict = {
"model.preview.label.average": "Custo Médio Est.",
"model.preview.label.context": "Contexto",
"model.preview.group.terminalBench": "Terminal Bench 2.0",
"model.preview.group.autoEfficientChoices": "Opções de modelo",
"model.preview.label.completion": "Conclusão",
"model.preview.label.costAttempt": "Custo / tentativa",
"model.preview.value.notSupported": "Não suportado",
+2
View File
@@ -183,6 +183,7 @@ export const dict = {
"model.tag.free": "Besplatno",
"model.tag.dataCollected": "Podaci se mogu koristiti za obuku",
"model.tag.latest": "Najnovije",
"model.group.auto": "Automatski modeli",
"model.group.recommended": "Preporučeno",
"model.group.favorites": "Favoriti",
"model.group.collapse": "Sakrij {{group}}",
@@ -210,6 +211,7 @@ export const dict = {
"model.preview.label.average": "Procj. prosječni trošak",
"model.preview.label.context": "Kontekst",
"model.preview.group.terminalBench": "Terminal Bench 2.0",
"model.preview.group.autoEfficientChoices": "Izbori modela",
"model.preview.label.completion": "Završetak",
"model.preview.label.costAttempt": "Trošak / pokušaj",
"model.preview.value.notSupported": "Nije podržano",
+2
View File
@@ -182,6 +182,7 @@ export const dict = {
"model.tag.free": "Gratis",
"model.tag.dataCollected": "Data kan bruges til træning",
"model.tag.latest": "Nyeste",
"model.group.auto": "Automatiske modeller",
"model.group.recommended": "Anbefalet",
"model.group.favorites": "Favoritter",
"model.group.collapse": "Skjul {{group}}",
@@ -210,6 +211,7 @@ export const dict = {
"model.preview.label.average": "Est. gennemsnitspris",
"model.preview.label.context": "Kontekst",
"model.preview.group.terminalBench": "Terminal Bench 2.0",
"model.preview.group.autoEfficientChoices": "Modelvalg",
"model.preview.label.completion": "Fuldførelse",
"model.preview.label.costAttempt": "Pris / forsøg",
"model.preview.value.notSupported": "Ikke understøttet",
+2
View File
@@ -184,6 +184,7 @@ export const dict = {
"model.tag.free": "Kostenlos",
"model.tag.dataCollected": "Daten können für das Training verwendet werden",
"model.tag.latest": "Neueste",
"model.group.auto": "Automatische Modelle",
"model.group.recommended": "Empfohlen",
"model.group.favorites": "Favoriten",
"model.group.collapse": "{{group}} einklappen",
@@ -212,6 +213,7 @@ export const dict = {
"model.preview.label.average": "Geschätzte Ø-Kosten",
"model.preview.label.context": "Kontext",
"model.preview.group.terminalBench": "Terminal Bench 2.0",
"model.preview.group.autoEfficientChoices": "Modellauswahl",
"model.preview.label.completion": "Abschluss",
"model.preview.label.costAttempt": "Kosten / Versuch",
"model.preview.value.notSupported": "Nicht unterstützt",
@@ -228,6 +228,7 @@ export const dict = {
"model.tag.free": "Free",
"model.tag.dataCollected": "Data may be used for training",
"model.tag.latest": "Latest",
"model.group.auto": "Auto Models",
"model.group.recommended": "Recommended",
"model.group.favorites": "Favorites",
"model.group.collapse": "Collapse {{group}}",
@@ -255,6 +256,7 @@ export const dict = {
"model.preview.label.average": "Est. Average Cost",
"model.preview.label.context": "Context",
"model.preview.group.terminalBench": "Terminal Bench 2.0",
"model.preview.group.autoEfficientChoices": "Model choices",
"model.preview.label.completion": "Completion",
"model.preview.label.costAttempt": "Cost / attempt",
"model.preview.value.notSupported": "Not supported",
+2
View File
@@ -183,6 +183,7 @@ export const dict = {
"model.tag.free": "Gratis",
"model.tag.dataCollected": "Los datos pueden utilizarse para entrenamiento",
"model.tag.latest": "Último",
"model.group.auto": "Modelos automáticos",
"model.group.recommended": "Recomendado",
"model.group.favorites": "Favoritos",
"model.group.collapse": "Contraer {{group}}",
@@ -211,6 +212,7 @@ export const dict = {
"model.preview.label.average": "Costo prom. est.",
"model.preview.label.context": "Contexto",
"model.preview.group.terminalBench": "Terminal Bench 2.0",
"model.preview.group.autoEfficientChoices": "Opciones de modelo",
"model.preview.label.completion": "Finalización",
"model.preview.label.costAttempt": "Costo / intento",
"model.preview.value.notSupported": "No admitido",
+2
View File
@@ -184,6 +184,7 @@ export const dict = {
"model.tag.free": "Gratuit",
"model.tag.dataCollected": "Les données peuvent être utilisées pour l’entraînement",
"model.tag.latest": "Dernier",
"model.group.auto": "Modèles automatiques",
"model.group.recommended": "Recommandé",
"model.group.favorites": "Favoris",
"model.group.collapse": "Réduire {{group}}",
@@ -212,6 +213,7 @@ export const dict = {
"model.preview.label.average": "Coût moyen est.",
"model.preview.label.context": "Contexte",
"model.preview.group.terminalBench": "Terminal Bench 2.0",
"model.preview.group.autoEfficientChoices": "Choix de modèles",
"model.preview.label.completion": "Réussite",
"model.preview.label.costAttempt": "Coût / tentative",
"model.preview.value.notSupported": "Non pris en charge",
+2
View File
@@ -158,6 +158,7 @@ export const dict = {
"model.tag.free": "Gratis",
"model.tag.dataCollected": "I dati possono essere utilizzati per l'addestramento",
"model.tag.latest": "Più recente",
"model.group.auto": "Modelli automatici",
"model.group.recommended": "Consigliati",
"model.group.favorites": "Preferiti",
"model.favorite.add": "Aggiungi ai preferiti",
@@ -183,6 +184,7 @@ export const dict = {
"model.preview.label.average": "Costo medio stimato",
"model.preview.label.context": "Contesto",
"model.preview.group.terminalBench": "Terminal Bench 2.0",
"model.preview.group.autoEfficientChoices": "Opzioni modello",
"model.preview.label.completion": "Completamento",
"model.preview.label.costAttempt": "Costo / tentativo",
"model.preview.value.notSupported": "Non supportato",
+2
View File
@@ -181,6 +181,7 @@ export const dict = {
"model.tag.free": "無料",
"model.tag.dataCollected": "データがトレーニングに使用される場合があります",
"model.tag.latest": "最新",
"model.group.auto": "自動モデル",
"model.group.recommended": "推奨",
"model.group.favorites": "お気に入り",
"model.group.collapse": "{{group}} を折りたたむ",
@@ -209,6 +210,7 @@ export const dict = {
"model.preview.label.average": "推定平均コスト",
"model.preview.label.context": "コンテキスト",
"model.preview.group.terminalBench": "Terminal Bench 2.0",
"model.preview.group.autoEfficientChoices": "モデルの選択肢",
"model.preview.label.completion": "完了率",
"model.preview.label.costAttempt": "試行あたりのコスト",
"model.preview.value.notSupported": "未対応",
+2
View File
@@ -183,6 +183,7 @@ export const dict = {
"model.tag.free": "무료",
"model.tag.dataCollected": "데이터가 학습에 사용될 수 있습니다",
"model.tag.latest": "최신",
"model.group.auto": "자동 모델",
"model.group.recommended": "추천",
"model.group.favorites": "즐겨찾기",
"model.group.collapse": "{{group}} 접기",
@@ -211,6 +212,7 @@ export const dict = {
"model.preview.label.average": "예상 평균 비용",
"model.preview.label.context": "컨텍스트",
"model.preview.group.terminalBench": "Terminal Bench 2.0",
"model.preview.group.autoEfficientChoices": "모델 선택지",
"model.preview.label.completion": "완료율",
"model.preview.label.costAttempt": "시도당 비용",
"model.preview.value.notSupported": "미지원",
+2
View File
@@ -183,6 +183,7 @@ export const dict = {
"model.tag.free": "Gratis",
"model.tag.dataCollected": "Gegevens kunnen worden gebruikt voor training",
"model.tag.latest": "Nieuwste",
"model.group.auto": "Automatische modellen",
"model.group.recommended": "Aanbevolen",
"model.group.favorites": "Favorieten",
"model.group.collapse": "{{group}} inklappen",
@@ -210,6 +211,7 @@ export const dict = {
"model.preview.label.average": "Gesch. gem. kosten",
"model.preview.label.context": "Context",
"model.preview.group.terminalBench": "Terminal Bench 2.0",
"model.preview.group.autoEfficientChoices": "Modelkeuzes",
"model.preview.label.completion": "Voltooiing",
"model.preview.label.costAttempt": "Kosten / poging",
"model.preview.value.notSupported": "Niet ondersteund",
+2
View File
@@ -183,6 +183,7 @@ export const dict = {
"model.tag.free": "Gratis",
"model.tag.dataCollected": "Data kan brukes til trening",
"model.tag.latest": "Nyeste",
"model.group.auto": "Automatiske modeller",
"model.group.recommended": "Anbefalt",
"model.group.favorites": "Favoritter",
"model.group.collapse": "Skjul {{group}}",
@@ -210,6 +211,7 @@ export const dict = {
"model.preview.label.average": "Est. snittkostnad",
"model.preview.label.context": "Kontekst",
"model.preview.group.terminalBench": "Terminal Bench 2.0",
"model.preview.group.autoEfficientChoices": "Modellvalg",
"model.preview.label.completion": "Fullføring",
"model.preview.label.costAttempt": "Kostnad / forsøk",
"model.preview.value.notSupported": "Ikke støttet",
+2
View File
@@ -182,6 +182,7 @@ export const dict = {
"model.tag.free": "Darmowy",
"model.tag.dataCollected": "Dane mogą być wykorzystywane do trenowania",
"model.tag.latest": "Najnowszy",
"model.group.auto": "Modele automatyczne",
"model.group.recommended": "Zalecane",
"model.group.favorites": "Ulubione",
"model.group.collapse": "Zwiń {{group}}",
@@ -209,6 +210,7 @@ export const dict = {
"model.preview.label.average": "Szac. średni koszt",
"model.preview.label.context": "Kontekst",
"model.preview.group.terminalBench": "Terminal Bench 2.0",
"model.preview.group.autoEfficientChoices": "Wybór modeli",
"model.preview.label.completion": "Ukończenie",
"model.preview.label.costAttempt": "Koszt / próbę",
"model.preview.value.notSupported": "Nieobsługiwane",
+2
View File
@@ -182,6 +182,7 @@ export const dict = {
"model.tag.free": "Бесплатно",
"model.tag.dataCollected": "Данные могут использоваться для обучения",
"model.tag.latest": "Последняя",
"model.group.auto": "Автоматические модели",
"model.group.recommended": "Рекомендуемые",
"model.group.favorites": "Избранное",
"model.group.collapse": "Свернуть {{group}}",
@@ -209,6 +210,7 @@ export const dict = {
"model.preview.label.average": "Средняя стоимость",
"model.preview.label.context": "Контекст",
"model.preview.group.terminalBench": "Terminal Bench 2.0",
"model.preview.group.autoEfficientChoices": "Варианты моделей",
"model.preview.label.completion": "Выполнение",
"model.preview.label.costAttempt": "Стоимость / попытка",
"model.preview.value.notSupported": "Не поддерживается",
+2
View File
@@ -182,6 +182,7 @@ export const dict = {
"model.tag.free": "ฟรี",
"model.tag.dataCollected": "ข้อมูลอาจถูกนำไปใช้ในการฝึก",
"model.tag.latest": "ล่าสุด",
"model.group.auto": "โมเดลอัตโนมัติ",
"model.group.recommended": "แนะนำ",
"model.group.favorites": "รายการโปรด",
"model.group.collapse": "ย่อ {{group}}",
@@ -209,6 +210,7 @@ export const dict = {
"model.preview.label.average": "ต้นทุนเฉลี่ยโดยประมาณ",
"model.preview.label.context": "บริบท",
"model.preview.group.terminalBench": "Terminal Bench 2.0",
"model.preview.group.autoEfficientChoices": "ตัวเลือกโมเดล",
"model.preview.label.completion": "ความสำเร็จ",
"model.preview.label.costAttempt": "ค่าใช้จ่าย / ครั้ง",
"model.preview.value.notSupported": "ไม่รองรับ",
+2
View File
@@ -182,6 +182,7 @@ export const dict = {
"model.tag.free": "Ücretsiz",
"model.tag.dataCollected": "Veriler eğitim için kullanılabilir",
"model.tag.latest": "En yeni",
"model.group.auto": "Otomatik Modeller",
"model.group.recommended": "Önerilen",
"model.group.favorites": "Favoriler",
"model.group.collapse": "{{group}} grubunu daralt",
@@ -209,6 +210,7 @@ export const dict = {
"model.preview.label.average": "Tahmini Ort. Maliyet",
"model.preview.label.context": "Bağlam",
"model.preview.group.terminalBench": "Terminal Bench 2.0",
"model.preview.group.autoEfficientChoices": "Model seçenekleri",
"model.preview.label.completion": "Tamamlama",
"model.preview.label.costAttempt": "Maliyet / deneme",
"model.preview.value.notSupported": "Desteklenmiyor",
+2
View File
@@ -183,6 +183,7 @@ export const dict = {
"model.tag.free": "Безкоштовно",
"model.tag.dataCollected": "Дані можуть використовуватися для навчання",
"model.tag.latest": "Остання",
"model.group.auto": "Автоматичні моделі",
"model.group.recommended": "Рекомендовані",
"model.group.favorites": "Обране",
"model.group.collapse": "Згорнути {{group}}",
@@ -210,6 +211,7 @@ export const dict = {
"model.preview.label.average": "Орієнт. сер. вартість",
"model.preview.label.context": "Контекст",
"model.preview.group.terminalBench": "Terminal Bench 2.0",
"model.preview.group.autoEfficientChoices": "Варіанти моделей",
"model.preview.label.completion": "Виконання",
"model.preview.label.costAttempt": "Вартість / спроба",
"model.preview.value.notSupported": "Не підтримується",
+2
View File
@@ -180,6 +180,7 @@ export const dict = {
"model.tag.free": "免费",
"model.tag.dataCollected": "数据可能会用于训练",
"model.tag.latest": "最新",
"model.group.auto": "自动模型",
"model.group.recommended": "推荐",
"model.group.favorites": "收藏夹",
"model.group.collapse": "折叠 {{group}}",
@@ -208,6 +209,7 @@ export const dict = {
"model.preview.label.average": "预估平均成本",
"model.preview.label.context": "上下文",
"model.preview.group.terminalBench": "Terminal Bench 2.0",
"model.preview.group.autoEfficientChoices": "模型选择",
"model.preview.label.completion": "完成率",
"model.preview.label.costAttempt": "成本 / 次尝试",
"model.preview.value.notSupported": "不支持",
+2
View File
@@ -180,6 +180,7 @@ export const dict = {
"model.tag.free": "免費",
"model.tag.dataCollected": "資料可能會用於訓練",
"model.tag.latest": "最新",
"model.group.auto": "自動模型",
"model.group.recommended": "推薦",
"model.group.favorites": "我的最愛",
"model.group.collapse": "折疊 {{group}}",
@@ -208,6 +209,7 @@ export const dict = {
"model.preview.label.average": "預估平均成本",
"model.preview.label.context": "上下文",
"model.preview.group.terminalBench": "Terminal Bench 2.0",
"model.preview.group.autoEfficientChoices": "模型選擇",
"model.preview.label.completion": "完成率",
"model.preview.label.costAttempt": "成本 / 次嘗試",
"model.preview.value.notSupported": "不支援",
@@ -38,6 +38,19 @@ export const ModelSelectorNoProviders: Story = {
}
const ACCESSIBLE_MODELS: EnrichedModel[] = [
{
id: "kilo-auto/efficient",
name: "Kilo Auto Efficient",
providerID: "kilo",
providerName: "Kilo",
recommendedIndex: 0,
options: {
description:
"Routes each request to the cheapest model that gets the job done, based on continuously benchmarked accuracy and cost.",
},
autoRouting: { models: ["google/gemini-2.5-flash", "anthropic/claude-sonnet-4.6"] },
},
{ id: "omega", name: "Omega", providerID: "openai", providerName: "OpenAI", recommendedIndex: 1 },
{ id: "alpha", name: "Alpha", providerID: "kilo", providerName: "Kilo" },
{ id: "bravo", name: "Bravo", providerID: "kilo", providerName: "Kilo" },
{ id: "charlie", name: "Charlie", providerID: "kilo", providerName: "Kilo" },
@@ -17,12 +17,14 @@
}
.model-selector-body .model-selector-list {
order: 3;
flex: 1;
min-height: 0;
max-height: none;
}
.model-selector-preview {
order: 1;
flex-shrink: 0;
overflow: hidden;
height: 0;
@@ -154,6 +156,40 @@
color: var(--vscode-editor-foreground);
}
.model-preview-candidates {
display: flex;
flex-direction: column;
gap: 4px;
}
.model-preview-candidate {
display: flex;
flex-direction: column;
gap: 1px;
min-width: 0;
padding: 3px 5px;
border-radius: 3px;
background: var(--vscode-textCodeBlock-background);
color: var(--vscode-editor-foreground);
}
.model-preview-candidate-name,
.model-preview-candidate-id {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.model-preview-candidate-name {
font-size: var(--kilo-font-size-11);
}
.model-preview-candidate-id {
font-family: var(--vscode-editor-font-family, monospace);
font-size: var(--kilo-font-size-10);
color: var(--vscode-descriptionForeground);
}
.model-preview-data-line {
grid-column: 1 / -1;
display: inline-flex;
@@ -202,6 +238,8 @@
}
.model-preview-free-data-icon,
.model-preview-auto-icon,
.model-selector-auto-icon,
.model-selector-free-data-icon {
display: inline-flex;
align-items: center;
@@ -209,12 +247,20 @@
flex-shrink: 0;
}
.model-preview-auto-icon,
.model-selector-auto-icon {
--icon-base: var(--vscode-list-warningForeground, var(--vscode-inputValidation-warningForeground, #9a6700));
color: var(--icon-base);
opacity: 0.95;
}
.model-selector-preview--visible {
/* height is set inline via previewHeight signal */
border-top-width: 0;
}
.model-selector-splitter {
order: 2;
flex-shrink: 0;
height: 5px;
cursor: row-resize;
@@ -262,6 +308,7 @@
}
.model-selector-search-wrapper {
order: 0;
display: flex;
align-items: center;
gap: 4px;
@@ -428,6 +475,12 @@
text-overflow: ellipsis;
}
.model-selector-item.active .model-selector-auto-icon {
--icon-base: var(--vscode-list-warningForeground, var(--vscode-inputValidation-warningForeground, #9a6700));
color: var(--icon-base);
opacity: 1;
}
.model-selector-data-badge [data-component="tag"] {
flex-shrink: 0;
background: var(--vscode-testing-iconPassed, #4caf50) !important;
@@ -16,6 +16,7 @@ export interface ProviderModel {
input?: { text: boolean; image: boolean; audio: boolean; video: boolean; pdf: boolean }
}
options?: { description?: string }
autoRouting?: { models: string[] }
recommendedIndex?: number
isFree?: boolean
mayTrainOnYourPrompts?: boolean
@@ -43,6 +43,11 @@ export const KILO_MODEL_SCHEMA_EXTENSIONS = {
avgAttemptCostUsd: Schema.Finite,
}),
),
autoRouting: optionalOmitUndefined(
Schema.Struct({
models: Schema.Array(Schema.String),
}),
),
ai_sdk_provider: Schema.optional(Schema.Literals(AI_SDK_PROVIDERS)),
}
@@ -59,6 +64,7 @@ export function patchModelsDevModel(providerID: string, source: any) {
mayTrainOnYourPrompts: source.mayTrainOnYourPrompts,
hasUserByokAvailable: source.hasUserByokAvailable,
terminalBench: source.terminalBench,
autoRouting: source.autoRouting,
ai_sdk_provider: source.ai_sdk_provider,
options: source.options ?? {},
}
@@ -76,6 +82,7 @@ export function patchConfigModel(cfg: any, existing: any) {
mayTrainOnYourPrompts: cfg.mayTrainOnYourPrompts ?? existing?.mayTrainOnYourPrompts,
hasUserByokAvailable: cfg.hasUserByokAvailable ?? existing?.hasUserByokAvailable,
terminalBench: existing?.terminalBench,
autoRouting: existing?.autoRouting,
ai_sdk_provider: cfg.ai_sdk_provider ?? existing?.ai_sdk_provider,
variants: cfg.variants
? mapValues(
@@ -0,0 +1,45 @@
import { describe, expect, test } from "bun:test"
import { Schema } from "effect"
import { Provider } from "../../src/provider/provider"
import { patchModelsDevModel } from "../../src/kilocode/provider/provider"
describe("Kilo provider model metadata", () => {
test("preserves Auto Efficient routing models from Models.dev data", () => {
const patch = patchModelsDevModel("kilo", {
autoRouting: { models: ["google/gemini-2.5-flash", "anthropic/claude-sonnet-4.6"] },
})
expect(patch.autoRouting).toEqual({
models: ["google/gemini-2.5-flash", "anthropic/claude-sonnet-4.6"],
})
})
test("Provider.Model schema accepts Auto Efficient routing models", () => {
const model = Schema.decodeUnknownSync(Provider.Model)({
id: "kilo-auto/efficient",
providerID: "kilo",
api: { id: "kilo", url: "https://kilocode.ai", npm: "@kilocode/kilo-gateway" },
name: "Kilo Auto Efficient",
family: "kilo-auto",
capabilities: {
temperature: true,
reasoning: false,
attachment: false,
toolcall: true,
input: { text: true, audio: false, image: false, video: false, pdf: false },
output: { text: true, audio: false, image: false, video: false, pdf: false },
interleaved: false,
},
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
limit: { context: 128000, output: 16384 },
status: "active",
options: {},
headers: {},
release_date: "2026-06-26",
variants: {},
autoRouting: { models: ["google/gemini-2.5-flash"] },
})
expect(model.autoRouting).toEqual({ models: ["google/gemini-2.5-flash"] })
})
})
+3
View File
@@ -1712,6 +1712,9 @@ export type Model = {
overallScore: number
avgAttemptCostUsd: number
}
autoRouting?: {
models: Array<string>
}
ai_sdk_provider?: "alibaba" | "anthropic" | "mistral" | "openai" | "openai-compatible" | "openrouter"
}
+13
View File
@@ -22562,6 +22562,19 @@
"required": ["overallScore", "avgAttemptCostUsd"],
"additionalProperties": false
},
"autoRouting": {
"type": "object",
"properties": {
"models": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": ["models"],
"additionalProperties": false
},
"ai_sdk_provider": {
"type": "string",
"enum": ["alibaba", "anthropic", "mistral", "openai", "openai-compatible", "openrouter"]