mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
Merge remote-tracking branch 'origin/main' into feat/vscode-routed-model-usage
This commit is contained in:
@@ -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
@@ -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 }}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"] })
|
||||
})
|
||||
})
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user