From 568b7c9d92c2c4c382f055ccc1cd35e91c75b136 Mon Sep 17 00:00:00 2001 From: "Zoo (VP)" Date: Mon, 10 Aug 2026 15:10:15 +0900 Subject: [PATCH 1/2] fix(kimi-code): correct maxTokens defaults and add server-side override Fixes #1215 - Update kimiCodeDefaultModelInfo.maxTokens from 32768 to 131072 - Add kimiCodeModelDefaults for per-model fallback values - Add max_tokens to kimiCodeModelSchema for server response parsing - Override maxTokens in mapKimiCodeModel: server > model-default > global-default - Add 4 new tests for maxTokens override and fallback behavior --- packages/types/src/providers/kimi-code.ts | 9 ++++- .../fetchers/__tests__/kimi-code.spec.ts | 35 +++++++++++++++++++ src/api/providers/fetchers/kimi-code.ts | 4 +++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/packages/types/src/providers/kimi-code.ts b/packages/types/src/providers/kimi-code.ts index c9a458ed1f..0837e25832 100644 --- a/packages/types/src/providers/kimi-code.ts +++ b/packages/types/src/providers/kimi-code.ts @@ -7,7 +7,7 @@ export const kimiCodeReasoningEfforts = ["low", "high", "max"] as const export const kimiCodeDefaultModelInfo: ModelInfo = { contextWindow: 262_144, - maxTokens: 32_768, + maxTokens: 131_072, supportsImages: false, supportsPromptCache: false, supportsReasoningEffort: [...kimiCodeReasoningEfforts], @@ -16,6 +16,13 @@ export const kimiCodeDefaultModelInfo: ModelInfo = { description: "Kimi Code's coding model for subscription and API-key access.", } +export const kimiCodeModelDefaults: Record = { + k3: { maxTokens: 131_072 }, + "k3-256k": { maxTokens: 131_072 }, + "kimi-for-coding": { maxTokens: 131_072 }, + "kimi-for-coding-highspeed": { maxTokens: 131_072 }, +} + export const kimiCodeModels = { [kimiCodeDefaultModelId]: kimiCodeDefaultModelInfo, } as const satisfies Record diff --git a/src/api/providers/fetchers/__tests__/kimi-code.spec.ts b/src/api/providers/fetchers/__tests__/kimi-code.spec.ts index 3fbe995752..93a69ad756 100644 --- a/src/api/providers/fetchers/__tests__/kimi-code.spec.ts +++ b/src/api/providers/fetchers/__tests__/kimi-code.spec.ts @@ -1,3 +1,5 @@ +import { kimiCodeDefaultModelInfo, kimiCodeModelDefaults } from "@roo-code/types" + import { getKimiCodeModels, mapKimiCodeModel } from "../kimi-code" describe("Kimi Code model discovery", () => { @@ -97,4 +99,37 @@ describe("Kimi Code model discovery", () => { expect(vi.mocked(fetch).mock.calls[0][1]?.signal?.aborted).toBe(true) expect(vi.getTimerCount()).toBe(0) }) + + it("overrides maxTokens from server max_tokens in mapKimiCodeModel", () => { + const mapped = mapKimiCodeModel({ + id: "kimi-for-coding", + max_tokens: 200_000, + }) + expect(mapped.maxTokens).toBe(200_000) + }) + + it("overrides maxTokens from server max_tokens at fetcher level in getKimiCodeModels", async () => { + vi.spyOn(globalThis, "fetch").mockResolvedValue( + new Response( + JSON.stringify({ + data: [{ id: "kimi-for-coding", context_length: 262144, max_tokens: 200_000 }], + }), + { status: 200 }, + ), + ) + const models = await getKimiCodeModels("token") + expect(models["kimi-for-coding"].maxTokens).toBe(200_000) + }) + + it("falls back to per-model defaults from kimiCodeModelDefaults", () => { + for (const [modelId, defaults] of Object.entries(kimiCodeModelDefaults)) { + const mapped = mapKimiCodeModel({ id: modelId }) + expect(mapped.maxTokens).toBe(defaults.maxTokens) + } + }) + + it("falls back to kimiCodeDefaultModelInfo.maxTokens for unknown model ids", () => { + const mapped = mapKimiCodeModel({ id: "unknown-model" }) + expect(mapped.maxTokens).toBe(kimiCodeDefaultModelInfo.maxTokens) + }) }) diff --git a/src/api/providers/fetchers/kimi-code.ts b/src/api/providers/fetchers/kimi-code.ts index 10b93e26cf..8168dbab18 100644 --- a/src/api/providers/fetchers/kimi-code.ts +++ b/src/api/providers/fetchers/kimi-code.ts @@ -3,6 +3,7 @@ import { z } from "zod" import { KIMI_CODE_BASE_URL, kimiCodeDefaultModelInfo, + kimiCodeModelDefaults, kimiCodeReasoningEfforts, type ModelInfo, type ModelRecord, @@ -11,6 +12,7 @@ import { const kimiCodeModelSchema = z.object({ id: z.string().min(1), context_length: z.number().positive().optional(), + max_tokens: z.number().positive().optional(), supports_reasoning: z.boolean().optional(), supports_image_in: z.boolean().optional(), display_name: z.string().optional(), @@ -22,9 +24,11 @@ const KIMI_CODE_MODELS_TIMEOUT_MS = 10_000 export function mapKimiCodeModel(model: z.infer): ModelInfo { const supportsReasoning = model.supports_reasoning ?? false + const defaults = kimiCodeModelDefaults[model.id] ?? {} return { ...kimiCodeDefaultModelInfo, contextWindow: model.context_length ?? kimiCodeDefaultModelInfo.contextWindow, + maxTokens: model.max_tokens ?? defaults.maxTokens ?? kimiCodeDefaultModelInfo.maxTokens, supportsReasoningEffort: supportsReasoning ? [...kimiCodeReasoningEfforts] : false, requiredReasoningEffort: supportsReasoning, reasoningEffort: supportsReasoning ? "max" : undefined, From 4cba8fd4fcdf471c2abd7e40b14f631a9f66fee4 Mon Sep 17 00:00:00 2001 From: "Zoo (VP)" Date: Mon, 10 Aug 2026 15:20:22 +0900 Subject: [PATCH 2/2] refactor(kimi-code): address PR review comments - Replace production constant imports in tests with hardcoded fixture values - Add .int() validation to max_tokens Zod schema - Add fractional max_tokens rejection test - Export kimiCodeModelSchema for test access --- .../fetchers/__tests__/kimi-code.spec.ts | 32 +++++++++++++++---- src/api/providers/fetchers/kimi-code.ts | 4 +-- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/api/providers/fetchers/__tests__/kimi-code.spec.ts b/src/api/providers/fetchers/__tests__/kimi-code.spec.ts index 93a69ad756..a96e760253 100644 --- a/src/api/providers/fetchers/__tests__/kimi-code.spec.ts +++ b/src/api/providers/fetchers/__tests__/kimi-code.spec.ts @@ -1,6 +1,4 @@ -import { kimiCodeDefaultModelInfo, kimiCodeModelDefaults } from "@roo-code/types" - -import { getKimiCodeModels, mapKimiCodeModel } from "../kimi-code" +import { getKimiCodeModels, kimiCodeModelSchema, mapKimiCodeModel } from "../kimi-code" describe("Kimi Code model discovery", () => { beforeEach(() => vi.restoreAllMocks()) @@ -121,15 +119,35 @@ describe("Kimi Code model discovery", () => { expect(models["kimi-for-coding"].maxTokens).toBe(200_000) }) - it("falls back to per-model defaults from kimiCodeModelDefaults", () => { - for (const [modelId, defaults] of Object.entries(kimiCodeModelDefaults)) { + it("falls back to per-model defaults for known model ids", () => { + const expected: Record = { + k3: 131_072, + "k3-256k": 131_072, + "kimi-for-coding": 131_072, + "kimi-for-coding-highspeed": 131_072, + } + for (const [modelId, maxTokens] of Object.entries(expected)) { const mapped = mapKimiCodeModel({ id: modelId }) - expect(mapped.maxTokens).toBe(defaults.maxTokens) + expect(mapped.maxTokens).toBe(maxTokens) } }) it("falls back to kimiCodeDefaultModelInfo.maxTokens for unknown model ids", () => { const mapped = mapKimiCodeModel({ id: "unknown-model" }) - expect(mapped.maxTokens).toBe(kimiCodeDefaultModelInfo.maxTokens) + expect(mapped.maxTokens).toBe(131_072) + }) + + it("rejects fractional max_tokens from server response", () => { + const mapped = mapKimiCodeModel({ + id: "kimi-for-coding", + max_tokens: 131072.5, + }) + // Zod .int() would reject at schema level, but mapKimiCodeModel receives + // already-parsed data. Verify the schema rejects fractional values. + const result = kimiCodeModelSchema.safeParse({ + id: "kimi-for-coding", + max_tokens: 131072.5, + }) + expect(result.success).toBe(false) }) }) diff --git a/src/api/providers/fetchers/kimi-code.ts b/src/api/providers/fetchers/kimi-code.ts index 8168dbab18..02bf9180f7 100644 --- a/src/api/providers/fetchers/kimi-code.ts +++ b/src/api/providers/fetchers/kimi-code.ts @@ -9,10 +9,10 @@ import { type ModelRecord, } from "@roo-code/types" -const kimiCodeModelSchema = z.object({ +export const kimiCodeModelSchema = z.object({ id: z.string().min(1), context_length: z.number().positive().optional(), - max_tokens: z.number().positive().optional(), + max_tokens: z.number().int().positive().optional(), supports_reasoning: z.boolean().optional(), supports_image_in: z.boolean().optional(), display_name: z.string().optional(),