diff --git a/extensions/copilot/src/extension/prompts/node/agent/allAgentPrompts.ts b/extensions/copilot/src/extension/prompts/node/agent/allAgentPrompts.ts index 098d8460fe8f2..29ff019aa07d2 100644 --- a/extensions/copilot/src/extension/prompts/node/agent/allAgentPrompts.ts +++ b/extensions/copilot/src/extension/prompts/node/agent/allAgentPrompts.ts @@ -6,6 +6,7 @@ import './anthropicPrompts'; import './familyHPrompts'; import './geminiPrompts'; +import './kimiPrompts'; import './minimaxPrompts'; import './vscModelPrompts'; // vscModelPrompts must be imported before gpt5Prompt to ensure VSC model prompt resolvers are registered first. @@ -21,4 +22,3 @@ import './openai/gpt5Prompt'; import './openai/hiddenModelMPrompt'; import './xAIPrompts'; import './zaiPrompts'; - diff --git a/extensions/copilot/src/extension/prompts/node/agent/kimiPrompts.tsx b/extensions/copilot/src/extension/prompts/node/agent/kimiPrompts.tsx new file mode 100644 index 0000000000000..30725b77ccff3 --- /dev/null +++ b/extensions/copilot/src/extension/prompts/node/agent/kimiPrompts.tsx @@ -0,0 +1,153 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { PromptElement, PromptSizing } from '@vscode/prompt-tsx'; +import { isKimiFamily } from '../../../../platform/endpoint/common/chatModelCapabilities'; +import { IChatEndpoint } from '../../../../platform/networking/common/networking'; +import { agenticBrowserTools, ToolName } from '../../../tools/common/toolNames'; +import { InstructionMessage } from '../base/instructionMessage'; +import { ResponseTranslationRules } from '../base/responseTranslationRules'; +import { Tag } from '../base/tag'; +import { EXISTING_CODE_MARKER } from '../panel/codeBlockFormattingRules'; +import { ResponseRenderingRules } from '../panel/editorIntegrationRules'; +import { ApplyPatchInstructions, CodesearchModeInstructions, DefaultAgentPromptProps, DefaultReminderInstructions, detectToolCapabilities, GenericEditingTips, McpToolInstructions, NotebookInstructions } from './defaultAgentInstructions'; +import { FileLinkificationInstructions } from './fileLinkificationInstructions'; +import { IAgentPrompt, PromptRegistry, ReminderInstructionsConstructor, SystemPrompt } from './promptRegistry'; + +class KimiAgentPrompt extends PromptElement { + async render(state: void, sizing: PromptSizing) { + const tools = detectToolCapabilities(this.props.availableTools); + + return + + You are an expert AI programming assistant, working with a user in the VS Code editor. You are a precise, practical coding agent with strong software engineering judgment across programming languages and frameworks.
+ Follow the user's requirements carefully and use the provided workspace context, attachments, and tool results as reference material. If the answer is not supported by the available context, gather more context before acting or state the limitation clearly. +
+ + + Use clear, step-by-step task execution:
+ - For simple questions or code samples, answer directly without unnecessary tool calls.
+ - For codebase questions, gather the smallest sufficient set of relevant context, then answer with concrete references.
+ - For implementation tasks, identify the controlling code path, make focused changes, and validate with the most relevant available checks.
+ - For feature requests without specified files, break the request into concepts and find the files responsible for those concepts before editing.
+ - Do not guess about APIs, file paths, or project conventions. Verify them using context or tools. +
+ + + Avoid excessive looping or repetition:
+ - If you find yourself running similar commands or re-editing the same files without clear progress, stop and reassess rather than continuing to loop.
+ - If an action fails or does not work as expected, do not retry it unchanged. Understand why it failed, then try a different approach.
+ - Never call the same tool with the same arguments more than twice in a row.
+ - If you are stuck or no longer making progress, end the turn with a concise summary of what you tried, what is blocked, and any clarifying question needed. +
+ + + Important: Use built-in tools instead of terminal commands whenever possible.
+ {tools[ToolName.ReadFile] && <>- Use {ToolName.ReadFile} instead of terminal commands like `cat`, `head`, or `tail` when reading known files.
} + {tools[ToolName.FindTextInFiles] && <>- Use {ToolName.FindTextInFiles} instead of terminal commands like `grep` or `rg` when searching file contents.
} + {tools[ToolName.FindFiles] && <>- Use {ToolName.FindFiles} instead of terminal commands like `find` or `ls` when looking for files.
} + {tools.hasSomeEditTool && <>- Use the available file editing tools instead of terminal heredocs, `sed`, `awk`, `echo`, or shell redirection to modify files.
} + {tools[ToolName.CoreRunInTerminal] && <>- Use {ToolName.CoreRunInTerminal} for commands that truly need execution, such as builds, tests, package managers, or project-specific scripts.
} +
+ + + You will be given context and attachments along with the user prompt. Use relevant context and ignore irrelevant context.{tools[ToolName.ReadFile] && <> Some attachments may be summarized with omitted sections like `/* Lines 123-456 omitted */`. Use {ToolName.ReadFile} to read more context if needed. Never pass this omitted line marker to an edit tool.}
+ If you can infer the project type (languages, frameworks, and libraries) from the user's query or the context, keep it in mind when making changes.
+ When reading files, prefer reading large meaningful chunks rather than consecutive small sections to minimize tool calls and gain better context.
+ You do not need to read a file if it is already provided in context. +
+ + + When using a tool, follow the JSON schema carefully and include all required properties.
+ No need to ask permission before using a tool.
+ NEVER say the name of a tool to a user. For example, instead of saying that you'll use the {ToolName.CoreRunInTerminal} tool, say "I'll run the command in a terminal".
+ If multiple independent tool calls can answer the user's question, prefer calling them in parallel whenever possible{tools[ToolName.Codebase] && <>, but do not call {ToolName.Codebase} in parallel}.
+ {(tools[ToolName.SearchSubagent] || tools[ToolName.ExploreSubagent]) && <>For efficient codebase exploration, prefer {tools[ToolName.SearchSubagent] ? ToolName.SearchSubagent : ToolName.ExploreSubagent} to search and gather data instead of directly calling {ToolName.FindTextInFiles}, {ToolName.Codebase} or {ToolName.FindFiles}.
} + {tools[ToolName.ExecutionSubagent] && <>For most execution tasks and terminal commands, use {ToolName.ExecutionSubagent} to run commands and get relevant portions of the output instead of using {ToolName.CoreRunInTerminal}. Use {ToolName.CoreRunInTerminal} only when you need the entire output of a single command without truncation.
} + {tools[ToolName.ReadFile] && <>When using {ToolName.ReadFile}, prefer reading a large section over many small sequential reads. Identify independent files or sections and read them in parallel when possible.
} + {tools[ToolName.Codebase] && <>If {ToolName.Codebase} returns the full contents of text files in the workspace, you have all the workspace context.
} + {tools[ToolName.FindTextInFiles] && <>Use {ToolName.FindTextInFiles} to get an overview of a file by searching within that one file instead of reading many small ranges.
} + {tools[ToolName.Codebase] && <>If you do not know the exact string or filename pattern to search for, use {ToolName.Codebase} for semantic search across the workspace.
} + {tools[ToolName.CoreRunInTerminal] && <>Do not call {ToolName.CoreRunInTerminal} multiple times in parallel. Run one command and wait for the output before running the next command.
} + {tools[ToolName.ExecutionSubagent] && <>Do not call {ToolName.ExecutionSubagent} multiple times in parallel. Invoke one execution subagent and wait for its response before running the next command.
} + When invoking a tool that takes a file path, always use the absolute file path. If the file has a scheme like untitled: or vscode-userdata:, use a URI with the scheme.
+ {tools[ToolName.CoreRunInTerminal] && <>NEVER try to edit a file by running terminal commands unless the user specifically asks for it.
} + {!tools.hasSomeEditTool && <>You do not currently have tools available for editing files. If the user asks you to edit a file, ask the user to enable editing tools or print a codeblock with suggested changes.
} + {!tools[ToolName.CoreRunInTerminal] && <>You do not currently have tools available for running terminal commands. If the user asks you to run a command, ask the user to enable terminal tools or print a codeblock with the suggested command.
} + {tools[ToolName.CoreOpenBrowserPage] && tools.hasAgenticBrowserTools && <>Use the browser tools ({ToolName.CoreOpenBrowserPage}, {agenticBrowserTools.find(k => tools[k])}, etc.) when beneficial for front-end tasks, such as visualizing or validating UI changes.
} + Tools can be disabled by the user. You may see tools used previously in the conversation that are not currently available. Only use tools that are currently available. +
+ + {this.props.codesearchMode && } + + {tools[ToolName.ReplaceString] && !tools[ToolName.EditFile] && + Before editing an existing file, make sure it is already in context or read it with {ToolName.ReadFile}.
+ {tools[ToolName.MultiReplaceString] + ? <>Use {ToolName.ReplaceString} for single string replacements with enough context to ensure uniqueness. Prefer {ToolName.MultiReplaceString} for multiple independent replacements across one or more files. Do not announce which tool you're using.
+ : <>Use {ToolName.ReplaceString} to edit files. Include sufficient surrounding context so the replacement is unique. You can use this tool multiple times per file.
} + Group changes by file.
+ NEVER show the changes to the user; call the edit tool and the edits will be applied and shown to the user.
+ NEVER print a codeblock that represents a change to a file. Use {ToolName.ReplaceString}{tools[ToolName.MultiReplaceString] ? ` or ${ToolName.MultiReplaceString}` : ''} instead.
+ For each file, give a short description of what needs to be changed, then use the edit tool.
+
} + + {tools[ToolName.EditFile] && !tools[ToolName.ApplyPatch] && + {tools[ToolName.ReplaceString] ? + <> + Before editing an existing file, make sure it is already in context or read it with {ToolName.ReadFile}.
+ {tools[ToolName.MultiReplaceString] + ? <>Use {ToolName.ReplaceString} for single string replacements with enough context to ensure uniqueness. Prefer {ToolName.MultiReplaceString} for multiple independent replacements across one or more files. Do not announce which tool you're using.
+ : <>Use {ToolName.ReplaceString} to edit files. Include sufficient surrounding context so the replacement is unique. You can use this tool multiple times per file.
} + Use {ToolName.EditFile} to insert code into a file only if {tools[ToolName.MultiReplaceString] ? `${ToolName.MultiReplaceString}/` : ''}{ToolName.ReplaceString} has failed.
+ Group changes by file.
+ NEVER show the changes to the user; call the edit tool and the edits will be applied and shown to the user.
+ NEVER print a codeblock that represents a change to a file. Use {ToolName.ReplaceString}{tools[ToolName.MultiReplaceString] ? `, ${ToolName.MultiReplaceString},` : ''} or {ToolName.EditFile} instead.
+ For each file, give a short description of what needs to be changed, then use the edit tool.
+ : <> + Do not edit an existing file without reading it first.
+ Use {ToolName.EditFile} to edit files. Group changes by file.
+ NEVER show the changes to the user; call the edit tool and the edits will be applied and shown to the user.
+ NEVER print a codeblock that represents a change to a file. Use {ToolName.EditFile} instead.
+ For each file, give a short description of what needs to be changed, then use {ToolName.EditFile}.
+ } + + The {ToolName.EditFile} tool can understand how to apply edits to the user's files; provide minimal hints and avoid repeating existing code.
+ When using {ToolName.EditFile}, use comments to represent unchanged regions. For example:
+ // {EXISTING_CODE_MARKER}
+ changed code
+ // {EXISTING_CODE_MARKER}
+
} + + {tools[ToolName.ApplyPatch] && } + {this.props.availableTools && } + + + + Use proper Markdown formatting. When referring to symbols (classes, methods, variables) in the user's workspace, wrap them in backticks. For file paths and line numbers, follow the fileLinkification section below.
+ + +
+ +
; + } +} + +class KimiPromptResolver implements IAgentPrompt { + static readonly familyPrefixes: string[] = []; + + static matchesModel(endpoint: IChatEndpoint): boolean { + return isKimiFamily(endpoint); + } + + resolveSystemPrompt(endpoint: IChatEndpoint): SystemPrompt | undefined { + return KimiAgentPrompt; + } + + resolveReminderInstructions(endpoint: IChatEndpoint): ReminderInstructionsConstructor | undefined { + return DefaultReminderInstructions; + } +} + +PromptRegistry.registerPrompt(KimiPromptResolver); diff --git a/extensions/copilot/src/extension/prompts/node/agent/test/kimiPrompts.spec.tsx b/extensions/copilot/src/extension/prompts/node/agent/test/kimiPrompts.spec.tsx new file mode 100644 index 0000000000000..288c8b5900173 --- /dev/null +++ b/extensions/copilot/src/extension/prompts/node/agent/test/kimiPrompts.spec.tsx @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Raw } from '@vscode/prompt-tsx'; +import type { LanguageModelToolInformation } from 'vscode'; +import { afterAll, beforeAll, expect, suite, test } from 'vitest'; +import { IChatMLFetcher } from '../../../../../platform/chat/common/chatMLFetcher'; +import { StaticChatMLFetcher } from '../../../../../platform/chat/test/common/staticChatMLFetcher'; +import { MockEndpoint } from '../../../../../platform/endpoint/test/node/mockEndpoint'; +import { messageToMarkdown } from '../../../../../platform/log/common/messageStringify'; +import { IResponseDelta } from '../../../../../platform/networking/common/fetch'; +import { ITestingServicesAccessor } from '../../../../../platform/test/node/services'; +import { IInstantiationService } from '../../../../../util/vs/platform/instantiation/common/instantiation'; +import { createExtensionUnitTestingServices } from '../../../../test/node/services'; +import { ToolName } from '../../../../tools/common/toolNames'; +import { IToolsService } from '../../../../tools/common/toolsService'; +import { PromptRenderer } from '../../base/promptRenderer'; +import '../allAgentPrompts'; +import { PromptRegistry } from '../promptRegistry'; + +suite('KimiPrompts', () => { + let accessor: ITestingServicesAccessor; + + beforeAll(() => { + const services = createExtensionUnitTestingServices(); + const chatResponse: (string | IResponseDelta[])[] = []; + services.define(IChatMLFetcher, new StaticChatMLFetcher(chatResponse)); + accessor = services.createTestingAccessor(); + }); + + afterAll(() => { + accessor.dispose(); + }); + + async function renderSystemPrompt(family: string, availableTools?: readonly LanguageModelToolInformation[]): Promise { + const instantiationService = accessor.get(IInstantiationService); + const endpoint = instantiationService.createInstance(MockEndpoint, family); + const customizations = await PromptRegistry.resolveAllCustomizations(instantiationService, endpoint); + const renderer = PromptRenderer.create(instantiationService, endpoint, customizations.SystemPrompt, { + availableTools: availableTools ?? accessor.get(IToolsService).tools, + modelFamily: family, + codesearchMode: false, + }); + const result = await renderer.render(); + return result.messages + .filter(message => message.role === Raw.ChatRole.System) + .map(message => messageToMarkdown(message)) + .join('\n\n'); + } + + test('uses Kimi-specific prompt for Kimi model families', async () => { + const renderedPrompts = await Promise.all([ + renderSystemPrompt('kimi-k2.6'), + renderSystemPrompt('kimi-k2.7-code'), + ]); + + for (const renderedPrompt of renderedPrompts) { + expect(renderedPrompt).toContain('Avoid excessive looping or repetition'); + expect(renderedPrompt).toContain('Never call the same tool with the same arguments more than twice in a row'); + expect(renderedPrompt).toContain('Use built-in tools instead of terminal commands whenever possible'); + expect(renderedPrompt).toContain('Use the available file editing tools instead of terminal heredocs'); + } + }); + + test('instructs Kimi to use replace-string tools when routed to them', async () => { + const toolsService = accessor.get(IToolsService); + const availableTools = toolsService.tools.filter(tool => tool.name !== ToolName.EditFile && tool.name !== ToolName.ApplyPatch); + const renderedPrompt = await renderSystemPrompt('kimi-k2.7-code', availableTools); + + expect(renderedPrompt).toContain(`Use ${ToolName.ReplaceString} for single string replacements`); + expect(renderedPrompt).toContain(`Prefer ${ToolName.MultiReplaceString} for multiple independent replacements`); + expect(renderedPrompt).not.toContain(`Use ${ToolName.EditFile}`); + expect(renderedPrompt).not.toContain(`Use ${ToolName.ApplyPatch}`); + }); +}); diff --git a/extensions/copilot/src/platform/endpoint/common/chatModelCapabilities.ts b/extensions/copilot/src/platform/endpoint/common/chatModelCapabilities.ts index fb725ff704d3c..5bbaa8b9b0392 100644 --- a/extensions/copilot/src/platform/endpoint/common/chatModelCapabilities.ts +++ b/extensions/copilot/src/platform/endpoint/common/chatModelCapabilities.ts @@ -166,6 +166,19 @@ export function isGpt53Codex(model: LanguageModelChat | IChatEndpoint | string) return family.startsWith('gpt-5.3-codex'); } +export function isKimiFamily(model: LanguageModelChat | IChatEndpoint | string): boolean { + const matches = (value: string): boolean => { + const normalized = value.toLowerCase(); + return normalized.startsWith('kimi-k2.6') || normalized.startsWith('kimi-k2.7-code'); + }; + + if (typeof model === 'string') { + return matches(model); + } + + return matches(model.family) || matches(getModelId(model)); +} + export function isVSCModelA(model: LanguageModelChat | IChatEndpoint) { const ID_hash = getCachedSha256Hash(getModelId(model)); @@ -261,14 +274,14 @@ export function modelPrefersJsonNotebookRepresentation(model: LanguageModelChat * Model supports replace_string_in_file as an edit tool. */ export function modelSupportsReplaceString(model: LanguageModelChat | IChatEndpoint): boolean { - return isGeminiFamily(model) || model.family.includes('grok-code') || modelSupportsMultiReplaceString(model) || isHiddenModelF(model) || isMinimaxFamily(model) || isHiddenFamilyH(model); + return isGeminiFamily(model) || model.family.includes('grok-code') || modelSupportsMultiReplaceString(model) || isHiddenModelF(model) || isMinimaxFamily(model) || isHiddenFamilyH(model) || isKimiFamily(model); } /** * Model supports multi_replace_string_in_file as an edit tool. */ export function modelSupportsMultiReplaceString(model: LanguageModelChat | IChatEndpoint): boolean { - return isAnthropicFamily(model) || isHiddenModelE(model) || isVSCModelReplaceStringSet(model) || isMinimaxFamily(model) || isHiddenFamilyH(model); + return isAnthropicFamily(model) || isHiddenModelE(model) || isVSCModelReplaceStringSet(model) || isMinimaxFamily(model) || isHiddenFamilyH(model) || isKimiFamily(model); } /** @@ -276,7 +289,7 @@ export function modelSupportsMultiReplaceString(model: LanguageModelChat | IChat * without needing insert_edit_into_file. */ export function modelCanUseReplaceStringExclusively(model: LanguageModelChat | IChatEndpoint): boolean { - return isAnthropicFamily(model) || model.family.includes('grok-code') || isHiddenModelE(model) || model.family.toLowerCase().includes('gemini-3') || isVSCModelReplaceStringSet(model) || isHiddenModelF(model) || isMinimaxFamily(model) || isHiddenFamilyH(model); + return isAnthropicFamily(model) || model.family.includes('grok-code') || isHiddenModelE(model) || model.family.toLowerCase().includes('gemini-3') || isVSCModelReplaceStringSet(model) || isHiddenModelF(model) || isMinimaxFamily(model) || isHiddenFamilyH(model) || isKimiFamily(model); } /** diff --git a/extensions/copilot/src/platform/endpoint/test/node/chatModelCapabilities.spec.ts b/extensions/copilot/src/platform/endpoint/test/node/chatModelCapabilities.spec.ts index 799a6c84ab784..d8322ee0180b0 100644 --- a/extensions/copilot/src/platform/endpoint/test/node/chatModelCapabilities.spec.ts +++ b/extensions/copilot/src/platform/endpoint/test/node/chatModelCapabilities.spec.ts @@ -8,7 +8,7 @@ import { ConfigKey, IConfigurationService } from '../../../configuration/common/ import { DefaultsOnlyConfigurationService } from '../../../configuration/common/defaultsOnlyConfigurationService'; import { InMemoryConfigurationService } from '../../../configuration/test/common/inMemoryConfigurationService'; import type { IChatEndpoint } from '../../../networking/common/networking'; -import { getModelCapabilityOverride, modelSupportsContextEditing, modelSupportsPDFDocuments, modelSupportsToolSearch } from '../../common/chatModelCapabilities'; +import { getModelCapabilityOverride, isKimiFamily, modelCanUseApplyPatchExclusively, modelCanUseReplaceStringExclusively, modelSupportsApplyPatch, modelSupportsContextEditing, modelSupportsMultiReplaceString, modelSupportsPDFDocuments, modelSupportsReplaceString, modelSupportsToolSearch } from '../../common/chatModelCapabilities'; function fakeModel(family: string, model: string = family) { return { family, model } as unknown as IChatEndpoint; @@ -41,6 +41,51 @@ describe('modelSupportsPDFDocuments', () => { }); }); +describe('Kimi edit tool capabilities', () => { + test('uses replace-string tools without insert-edit or apply-patch', () => { + const models = { + 'kimi-k2.6': fakeModel('kimi-k2.6'), + 'kimi-k2.7-code': fakeModel('kimi-k2.7-code'), + 'unknown-family + kimi model id': fakeModel('unknown-family', 'kimi-k2.7-code-preview'), + }; + const actual = Object.fromEntries(Object.entries(models).map(([name, model]) => [name, { + isKimiFamily: isKimiFamily(model), + supportsReplaceString: modelSupportsReplaceString(model), + supportsMultiReplaceString: modelSupportsMultiReplaceString(model), + canUseReplaceStringExclusively: modelCanUseReplaceStringExclusively(model), + supportsApplyPatch: modelSupportsApplyPatch(model), + canUseApplyPatchExclusively: modelCanUseApplyPatchExclusively(model), + }])); + + expect(actual).toEqual({ + 'kimi-k2.6': { + isKimiFamily: true, + supportsReplaceString: true, + supportsMultiReplaceString: true, + canUseReplaceStringExclusively: true, + supportsApplyPatch: false, + canUseApplyPatchExclusively: false, + }, + 'kimi-k2.7-code': { + isKimiFamily: true, + supportsReplaceString: true, + supportsMultiReplaceString: true, + canUseReplaceStringExclusively: true, + supportsApplyPatch: false, + canUseApplyPatchExclusively: false, + }, + 'unknown-family + kimi model id': { + isKimiFamily: true, + supportsReplaceString: true, + supportsMultiReplaceString: true, + canUseReplaceStringExclusively: true, + supportsApplyPatch: false, + canUseApplyPatchExclusively: false, + }, + }); + }); +}); + describe('modelSupportsToolSearch', () => { test('supports Claude Sonnet/Opus 4.5 and up, including new and future families', () => { expect(modelSupportsToolSearch('claude-sonnet-4-5')).toBe(true);