diff --git a/.changeset/loud-lights-build.md b/.changeset/loud-lights-build.md new file mode 100644 index 00000000000..4d06f9cafc0 --- /dev/null +++ b/.changeset/loud-lights-build.md @@ -0,0 +1,5 @@ +--- +"kilo-code": minor +--- + +Added gemini-3-flash-preview model diff --git a/packages/types/src/providers/gemini.ts b/packages/types/src/providers/gemini.ts index 0247ada952d..7df7aca9185 100644 --- a/packages/types/src/providers/gemini.ts +++ b/packages/types/src/providers/gemini.ts @@ -3,7 +3,7 @@ import type { ModelInfo } from "../model.js" // https://ai.google.dev/gemini-api/docs/models/gemini export type GeminiModelId = keyof typeof geminiModels -export const geminiDefaultModelId: GeminiModelId = "gemini-3-pro-preview" // kilocode_change +export const geminiDefaultModelId: GeminiModelId = "gemini-3-pro-preview" export const geminiModels = { "gemini-3-pro-preview": { @@ -31,6 +31,22 @@ export const geminiModels = { }, ], }, + "gemini-3-flash-preview": { + maxTokens: 65_536, + contextWindow: 1_048_576, + supportsImages: true, + supportsNativeTools: true, + defaultToolProtocol: "native", + supportsPromptCache: true, + supportsReasoningEffort: ["minimal", "low", "medium", "high"], + reasoningEffort: "medium", + supportsTemperature: true, + defaultTemperature: 1, + inputPrice: 0.3, + outputPrice: 2.5, + cacheReadsPrice: 0.075, + cacheWritesPrice: 1.0, + }, // 2.5 Pro models "gemini-2.5-pro": { maxTokens: 64_000, diff --git a/packages/types/src/providers/vertex.ts b/packages/types/src/providers/vertex.ts index db7c90edafe..265d4d81fc2 100644 --- a/packages/types/src/providers/vertex.ts +++ b/packages/types/src/providers/vertex.ts @@ -6,19 +6,6 @@ export type VertexModelId = keyof typeof vertexModels export const vertexDefaultModelId: VertexModelId = "claude-sonnet-4-5@20250929" export const vertexModels = { - "gemini-3-flash-preview": { - maxTokens: 65_536, - contextWindow: 1_048_576, - supportsImages: true, - supportsNativeTools: true, - supportsPromptCache: true, - supportsReasoningEffort: ["minimal", "low", "medium", "high"], - reasoningEffort: "medium", - supportsTemperature: true, - defaultTemperature: 1, - inputPrice: 0.5, - outputPrice: 3.0, - }, "gemini-3-pro-preview": { maxTokens: 65_536, contextWindow: 1_048_576, @@ -44,6 +31,22 @@ export const vertexModels = { }, ], }, + "gemini-3-flash-preview": { + maxTokens: 65_536, + contextWindow: 1_048_576, + supportsImages: true, + supportsNativeTools: true, + defaultToolProtocol: "native", + supportsPromptCache: true, + supportsReasoningEffort: ["minimal", "low", "medium", "high"], + reasoningEffort: "medium", + supportsTemperature: true, + defaultTemperature: 1, + inputPrice: 0.3, + outputPrice: 2.5, + cacheReadsPrice: 0.075, + cacheWritesPrice: 1.0, + }, "gemini-2.5-flash-preview-05-20:thinking": { maxTokens: 65_535, contextWindow: 1_048_576, diff --git a/src/services/continuedev/core/autocomplete/postprocessing/__tests__/removePrefixOverlap.test.ts b/src/services/continuedev/core/autocomplete/postprocessing/__tests__/removePrefixOverlap.test.ts new file mode 100644 index 00000000000..c9b6ef38d5c --- /dev/null +++ b/src/services/continuedev/core/autocomplete/postprocessing/__tests__/removePrefixOverlap.test.ts @@ -0,0 +1,33 @@ +import { describe, it, expect } from "vitest" +import { removePrefixOverlap } from "../removePrefixOverlap.js" + +describe("removePrefixOverlap", () => { + it("should remove full prefix match", () => { + expect(removePrefixOverlap("hello world", "hello ")).toBe("world") + }) + + it("should remove trimmed prefix when prefix has extra whitespace", () => { + expect(removePrefixOverlap("hello world", " hello ")).toBe(" world") + }) + + it("should remove partial word overlap", () => { + expect(removePrefixOverlap("hello world", "hel")).toBe("lo world") + }) + + it("should return completion as-is when no overlap", () => { + expect(removePrefixOverlap("world", "hello")).toBe("world") + }) + + it("should use last line of multi-line prefix", () => { + expect(removePrefixOverlap("hello world", "line 1\nline 2\nhello ")).toBe("world") + }) + + it("should handle empty strings", () => { + expect(removePrefixOverlap("hello world", "")).toBe("hello world") + expect(removePrefixOverlap("", "hello")).toBe("") + }) + + it("should be case-sensitive", () => { + expect(removePrefixOverlap("hello world", "HELLO ")).toBe("hello world") + }) +}) diff --git a/src/services/continuedev/core/autocomplete/postprocessing/index.ts b/src/services/continuedev/core/autocomplete/postprocessing/index.ts index 1dd9594762f..950840ca94d 100644 --- a/src/services/continuedev/core/autocomplete/postprocessing/index.ts +++ b/src/services/continuedev/core/autocomplete/postprocessing/index.ts @@ -1,5 +1,6 @@ import { longestCommonSubsequence } from "../../util/lcs.js" import { lineIsRepeated } from "../util/textSimilarity.js" +import { removePrefixOverlap } from "./removePrefixOverlap.js" function rewritesLineAbove(completion: string, prefix: string): boolean { const lineAbove = prefix @@ -143,21 +144,7 @@ export function postprocessCompletion({ } if (llm.model.includes("mercury") || llm.model.includes("granite")) { - // Granite tends to repeat the start of the line in the completion output - const prefixEnd = prefix.split("\n").pop() - if (prefixEnd) { - if (completion.startsWith(prefixEnd)) { - completion = completion.slice(prefixEnd.length) - } else { - const trimmedPrefix = prefixEnd.trim() - const lastWord = trimmedPrefix.split(/\s+/).pop() - if (lastWord && completion.startsWith(lastWord)) { - completion = completion.slice(lastWord.length) - } else if (completion.startsWith(trimmedPrefix)) { - completion = completion.slice(trimmedPrefix.length) - } - } - } + completion = removePrefixOverlap(completion, prefix) } // // If completion starts with multiple whitespaces, but the cursor is at the end of the line diff --git a/src/services/continuedev/core/autocomplete/postprocessing/removePrefixOverlap.ts b/src/services/continuedev/core/autocomplete/postprocessing/removePrefixOverlap.ts new file mode 100644 index 00000000000..3d924db713e --- /dev/null +++ b/src/services/continuedev/core/autocomplete/postprocessing/removePrefixOverlap.ts @@ -0,0 +1,17 @@ +export function removePrefixOverlap(completion: string, prefix: string): string { + const prefixEnd = prefix.split("\n").pop() + if (prefixEnd) { + if (completion.startsWith(prefixEnd)) { + completion = completion.slice(prefixEnd.length) + } else { + const trimmedPrefix = prefixEnd.trim() + const lastWord = trimmedPrefix.split(/\s+/).pop() + if (lastWord && completion.startsWith(lastWord)) { + completion = completion.slice(lastWord.length) + } else if (completion.startsWith(trimmedPrefix)) { + completion = completion.slice(trimmedPrefix.length) + } + } + } + return completion +} diff --git a/src/services/ghost/chat-autocomplete/ChatTextAreaAutocomplete.ts b/src/services/ghost/chat-autocomplete/ChatTextAreaAutocomplete.ts index e3e0ae90604..3d7ad42d540 100644 --- a/src/services/ghost/chat-autocomplete/ChatTextAreaAutocomplete.ts +++ b/src/services/ghost/chat-autocomplete/ChatTextAreaAutocomplete.ts @@ -3,6 +3,7 @@ import { GhostModel } from "../GhostModel" import { ProviderSettingsManager } from "../../../core/config/ProviderSettingsManager" import { VisibleCodeContext } from "../types" import { ApiStreamChunk } from "../../../api/transform/stream" +import { removePrefixOverlap } from "../../continuedev/core/autocomplete/postprocessing/removePrefixOverlap.js" /** * Service for providing FIM-based autocomplete suggestions in ChatTextArea @@ -64,6 +65,9 @@ export class ChatTextAreaAutocomplete { } const cleanedSuggestion = this.cleanSuggestion(response, userText) + console.log( + `[ChatAutocomplete] prefix: ${JSON.stringify(userText)} | response: ${JSON.stringify(response)} | cleanedSuggestion: ${JSON.stringify(cleanedSuggestion)}`, + ) return { suggestion: cleanedSuggestion } } @@ -81,6 +85,8 @@ export class ChatTextAreaAutocomplete { - Use context from visible code if relevant - NEVER repeat what the user already typed - NEVER start with comments (//, /*, #) +- If the user is in the middle of typing a word (e.g., "hel"), include the COMPLETE word in your response (e.g., "hello world" not just "lo world") +- This allows proper prefix matching to remove the overlap correctly - Return ONLY the completion text, no explanations or formatting` } @@ -90,7 +96,9 @@ export class ChatTextAreaAutocomplete { private getChatUserPrompt(prefix: string): string { return `${prefix} -TASK: Complete the user's message naturally. Return ONLY the completion text (what comes next), no explanations.` +TASK: Complete the user's message naturally. +- If the user is mid-word (e.g., typed "hel"), return the COMPLETE word (e.g., "hello world") so prefix matching can work correctly +- Return ONLY the completion text (what comes next), no explanations.` } /** @@ -146,18 +154,15 @@ TASK: Complete the user's message naturally. Return ONLY the completion text (wh * and filtering out unwanted patterns like comments */ private cleanSuggestion(suggestion: string, userText: string): string { - let cleaned = suggestion.trim() + let cleaned = suggestion - if (cleaned.startsWith(userText)) { - cleaned = cleaned.substring(userText.length) - } + cleaned = removePrefixOverlap(cleaned, userText) const firstNewline = cleaned.indexOf("\n") if (firstNewline !== -1) { cleaned = cleaned.substring(0, firstNewline) } - - cleaned = cleaned.trimStart() + cleaned = cleaned.trimEnd() // Do NOT trim the end of the suggestion // Filter out suggestions that start with comment patterns // This happens because the context uses // prefixes for labels