From 7f302f368e79fe93c5ff1de462916a9790d0c834 Mon Sep 17 00:00:00 2001 From: Levi Hadad Date: Tue, 18 Aug 2026 14:14:39 +0300 Subject: [PATCH 1/2] refactor(sync): prioritize apiDescription over existing model description Update the model building logic to prefer the description provided by the API over the locally cached description. This ensures that updates to model descriptions from the provider are reflected in the local catalog during synchronization. --- packages/core/src/sync/providers/kilo.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/sync/providers/kilo.ts b/packages/core/src/sync/providers/kilo.ts index 6566eb2316..a68cdb4311 100644 --- a/packages/core/src/sync/providers/kilo.ts +++ b/packages/core/src/sync/providers/kilo.ts @@ -187,7 +187,7 @@ export function buildKiloModel( canonical, { name: baseModel !== undefined || model.id.endsWith(":free") ? name : undefined, - description: existing?.description ?? apiDescription ?? describeModel({ + description: apiDescription ?? existing?.description ?? describeModel({ id: model.id, name, family: familyValue, From 3d66f9f7525b6e047c2384794259484ef73e4ef3 Mon Sep 17 00:00:00 2001 From: Levi Hadad Date: Tue, 18 Aug 2026 15:00:16 +0300 Subject: [PATCH 2/2] fix(sync): apply API description priority consistently in Kilo sync - Use apiDescription-first order in both base-model and standalone paths. - Treat empty/whitespace API descriptions as non-authoritative so they fall back to existing authored or generated descriptions. - Add regression tests for description priority covering both code paths. --- packages/core/src/sync/providers/kilo.ts | 4 +- packages/core/test/sync.test.ts | 76 ++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/packages/core/src/sync/providers/kilo.ts b/packages/core/src/sync/providers/kilo.ts index a68cdb4311..9fa72e836a 100644 --- a/packages/core/src/sync/providers/kilo.ts +++ b/packages/core/src/sync/providers/kilo.ts @@ -187,7 +187,7 @@ export function buildKiloModel( canonical, { name: baseModel !== undefined || model.id.endsWith(":free") ? name : undefined, - description: apiDescription ?? existing?.description ?? describeModel({ + description: apiDescription || existing?.description || describeModel({ id: model.id, name, family: familyValue, @@ -217,7 +217,7 @@ export function buildKiloModel( return { name, - description: existing?.description ?? apiDescription ?? describeModel({ + description: apiDescription || existing?.description || describeModel({ id: model.id, name, family: familyValue, diff --git a/packages/core/test/sync.test.ts b/packages/core/test/sync.test.ts index 417caa7ea6..05bad93d3a 100644 --- a/packages/core/test/sync.test.ts +++ b/packages/core/test/sync.test.ts @@ -37,6 +37,7 @@ import { type EdenAIModel, } from "../src/sync/providers/edenai.js"; import { buildHyperModel, type HyperModel } from "../src/sync/providers/hyper.js"; +import { buildKiloModel, type KiloModel } from "../src/sync/providers/kilo.js"; import { buildInceptronModel, parseInceptronModels, @@ -139,6 +140,37 @@ function nanoGptModel(overrides: Partial = {}): NanoGptModel { }; } +function kiloModel(overrides: Partial = {}): KiloModel { + return { + id: "anthropic/claude-opus-4-6", + name: "Claude Opus 4.6", + description: "API description from Kilo", + created: 1_782_777_600, + hugging_face_id: null, + knowledge_cutoff: "2026-01-31", + context_length: 1_000_000, + architecture: { + input_modalities: ["text", "image"], + output_modalities: ["text"], + tokenizer: "claude", + }, + pricing: { + prompt: "0.000005", + completion: "0.000025", + internal_reasoning: "0.000025", + input_cache_read: "0.00000025", + input_cache_write: "0.00000375", + }, + top_provider: { + context_length: 1_000_000, + max_completion_tokens: 128_000, + is_moderated: false, + }, + supported_parameters: ["tools", "structured_outputs", "temperature"], + ...overrides, + }; +} + function crossModelModel(overrides: Partial = {}): CrossModelModel { return { id: "qwen/qwen3.8-max", @@ -841,6 +873,50 @@ test("NanoGPT sync drops stale descriptions while first factoring existing model } }); +test("Kilo sync prefers API description over existing description", () => { + const model = buildKiloModel( + kiloModel({ description: "Updated API description" }), + { description: "Existing authored description" } as ExistingModel, + ); + expect((model as SyncedFullModel).description).toBe("Updated API description"); +}); + +test("Kilo sync falls back to existing description when API description is empty", () => { + const model = buildKiloModel( + kiloModel({ description: "" }), + { description: "Existing authored description" } as ExistingModel, + ); + expect((model as SyncedFullModel).description).toBe("Existing authored description"); +}); + +test("Kilo sync falls back to generated description when API and existing descriptions are missing", () => { + const model = buildKiloModel( + kiloModel({ description: undefined }), + undefined, + ); + expect((model as SyncedFullModel).description).toBe( + "Flagship Claude model for deep reasoning, coding, and long-horizon agents", + ); +}); + +test("Kilo sync prefers API description over existing description for base-model factored models", () => { + const model = buildKiloModel( + kiloModel({ description: "Updated API description" }), + { description: "Existing authored description" } as ExistingModel, + "anthropic/claude-opus-4-6", + ); + expect(model.description).toBe("Updated API description"); +}); + +test("Kilo sync falls back to existing description when API description is empty for base-model factored models", () => { + const model = buildKiloModel( + kiloModel({ description: " " }), + { description: "Existing authored description" } as ExistingModel, + "anthropic/claude-opus-4-6", + ); + expect(model.description).toBe("Existing authored description"); +}); + const anthropicPricingMarkdown = ` ## Model pricing