mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
Add VS Code LM API
This commit is contained in:
parent
8d7b70b1e5
commit
8ec0b2cf08
15 changed files with 916 additions and 17 deletions
12
package-lock.json
generated
12
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "claude-dev",
|
||||
"version": "3.1.8",
|
||||
"version": "3.1.11",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "claude-dev",
|
||||
"version": "3.1.8",
|
||||
"version": "3.1.11",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/bedrock-sdk": "^0.10.2",
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
"@types/mocha": "^10.0.7",
|
||||
"@types/node": "20.x",
|
||||
"@types/should": "^11.2.0",
|
||||
"@types/vscode": "^1.84.0",
|
||||
"@types/vscode": "^1.96.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.14.1",
|
||||
"@typescript-eslint/parser": "^7.11.0",
|
||||
"@vscode/test-cli": "^0.0.9",
|
||||
|
|
@ -4641,9 +4641,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/vscode": {
|
||||
"version": "1.84.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.84.0.tgz",
|
||||
"integrity": "sha512-lCGOSrhT3cL+foUEqc8G1PVZxoDbiMmxgnUZZTEnHF4mC47eKAUtBGAuMLY6o6Ua8PAuNCoKXbqPmJd1JYnQfg==",
|
||||
"version": "1.96.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.96.0.tgz",
|
||||
"integrity": "sha512-qvZbSZo+K4ZYmmDuaodMbAa67Pl6VDQzLKFka6rq+3WUTY4Kro7Bwoi0CuZLO/wema0ygcmpwow7zZfPJTs5jg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
|
|
|||
21
package.json
21
package.json
|
|
@ -124,6 +124,25 @@
|
|||
"when": "view == claude-dev.SidebarProvider"
|
||||
}
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"title": "Cline",
|
||||
"properties": {
|
||||
"cline.vsCodeLmModelSelector": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"vendor": {
|
||||
"type": "string",
|
||||
"description": "The vendor of the language model (e.g. copilot)"
|
||||
},
|
||||
"family": {
|
||||
"type": "string",
|
||||
"description": "The family of the language model (e.g. gpt-4)"
|
||||
}
|
||||
},
|
||||
"description": "Settings for VSCode Language Model API"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
|
@ -152,7 +171,7 @@
|
|||
"@types/mocha": "^10.0.7",
|
||||
"@types/node": "20.x",
|
||||
"@types/should": "^11.2.0",
|
||||
"@types/vscode": "^1.84.0",
|
||||
"@types/vscode": "^1.96.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.14.1",
|
||||
"@typescript-eslint/parser": "^7.11.0",
|
||||
"@vscode/test-cli": "^0.0.9",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { OpenAiNativeHandler } from "./providers/openai-native"
|
|||
import { ApiStream } from "./transform/stream"
|
||||
import { DeepSeekHandler } from "./providers/deepseek"
|
||||
import { MistralHandler } from "./providers/mistral"
|
||||
import { VsCodeLmHandler } from "./providers/vscode-lm"
|
||||
|
||||
export interface ApiHandler {
|
||||
createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], modelType?: ModelType): ApiStream
|
||||
|
|
@ -19,6 +20,10 @@ export interface ApiHandler {
|
|||
getAdvisorModel?(): { id: string; info: ModelInfo }
|
||||
}
|
||||
|
||||
export interface SingleCompletionHandler {
|
||||
completePrompt(prompt: string): Promise<string>
|
||||
}
|
||||
|
||||
export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
|
||||
const { apiProvider, ...options } = configuration
|
||||
switch (apiProvider) {
|
||||
|
|
@ -44,6 +49,8 @@ export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
|
|||
return new DeepSeekHandler(options)
|
||||
case "mistral":
|
||||
return new MistralHandler(options)
|
||||
case "vscode-lm":
|
||||
return new VsCodeLmHandler(options)
|
||||
default:
|
||||
return new AnthropicHandler(options)
|
||||
}
|
||||
|
|
|
|||
547
src/api/providers/vscode-lm.ts
Normal file
547
src/api/providers/vscode-lm.ts
Normal file
|
|
@ -0,0 +1,547 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import * as vscode from "vscode"
|
||||
import { ApiHandler, SingleCompletionHandler } from "../"
|
||||
import { calculateApiCost } from "../../utils/cost"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { convertToVsCodeLmMessages } from "../transform/vscode-lm-format"
|
||||
import { SELECTOR_SEPARATOR, stringifyVsCodeLmModelSelector } from "../../shared/vsCodeSelectorUtils"
|
||||
import { ApiHandlerOptions, ModelInfo, openAiModelInfoSaneDefaults } from "../../shared/api"
|
||||
|
||||
/**
|
||||
* Handles interaction with VS Code's Language Model API for chat-based operations.
|
||||
* This handler implements the ApiHandler interface to provide VS Code LM specific functionality.
|
||||
*
|
||||
* @implements {ApiHandler}
|
||||
*
|
||||
* @remarks
|
||||
* The handler manages a VS Code language model chat client and provides methods to:
|
||||
* - Create and manage chat client instances
|
||||
* - Stream messages using VS Code's Language Model API
|
||||
* - Retrieve model information
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const options = {
|
||||
* vsCodeLmModelSelector: { vendor: "copilot", family: "gpt-4" }
|
||||
* };
|
||||
* const handler = new VsCodeLmHandler(options);
|
||||
*
|
||||
* // Stream a conversation
|
||||
* const systemPrompt = "You are a helpful assistant";
|
||||
* const messages = [{ role: "user", content: "Hello!" }];
|
||||
* for await (const chunk of handler.createMessage(systemPrompt, messages)) {
|
||||
* console.log(chunk);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private client: vscode.LanguageModelChat | null
|
||||
private disposable: vscode.Disposable | null
|
||||
private currentRequestCancellation: vscode.CancellationTokenSource | null
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
this.client = null
|
||||
this.disposable = null
|
||||
this.currentRequestCancellation = null
|
||||
|
||||
try {
|
||||
// Listen for model changes and reset client
|
||||
this.disposable = vscode.workspace.onDidChangeConfiguration((event) => {
|
||||
if (event.affectsConfiguration("lm")) {
|
||||
try {
|
||||
this.client = null
|
||||
this.ensureCleanState()
|
||||
} catch (error) {
|
||||
console.error("Error during configuration change cleanup:", error)
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
// Ensure cleanup if constructor fails
|
||||
this.dispose()
|
||||
|
||||
throw new Error(
|
||||
`Cline <Language Model API>: Failed to initialize handler: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a language model chat client based on the provided selector.
|
||||
*
|
||||
* @param selector - Selector criteria to filter language model chat instances
|
||||
* @returns Promise resolving to the first matching language model chat instance
|
||||
* @throws Error when no matching models are found with the given selector
|
||||
*
|
||||
* @example
|
||||
* const selector = { vendor: "copilot", family: "gpt-4o" };
|
||||
* const chatClient = await createClient(selector);
|
||||
*/
|
||||
async createClient(selector: vscode.LanguageModelChatSelector): Promise<vscode.LanguageModelChat> {
|
||||
try {
|
||||
const models = await vscode.lm.selectChatModels(selector)
|
||||
|
||||
// Use first available model or create a minimal model object
|
||||
if (models && Array.isArray(models) && models.length > 0) {
|
||||
return models[0]
|
||||
}
|
||||
|
||||
// Create a minimal model if no models are available
|
||||
return {
|
||||
id: "default-lm",
|
||||
name: "Default Language Model",
|
||||
vendor: "vscode",
|
||||
family: "lm",
|
||||
version: "1.0",
|
||||
maxInputTokens: 8192,
|
||||
sendRequest: async (messages, options, token) => {
|
||||
// Provide a minimal implementation
|
||||
return {
|
||||
stream: (async function* () {
|
||||
yield new vscode.LanguageModelTextPart(
|
||||
"Language model functionality is limited. Please check VS Code configuration.",
|
||||
)
|
||||
})(),
|
||||
text: (async function* () {
|
||||
yield "Language model functionality is limited. Please check VS Code configuration."
|
||||
})(),
|
||||
}
|
||||
},
|
||||
countTokens: async () => 0,
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : "Unknown error"
|
||||
throw new Error(`Cline <Language Model API>: Failed to select model: ${errorMessage}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and streams a message using the VS Code Language Model API.
|
||||
*
|
||||
* @param systemPrompt - The system prompt to initialize the conversation context
|
||||
* @param messages - An array of message parameters following the Anthropic message format
|
||||
*
|
||||
* @yields {ApiStream} An async generator that yields either text chunks or tool calls from the model response
|
||||
*
|
||||
* @throws {Error} When vsCodeLmModelSelector option is not provided
|
||||
* @throws {Error} When the response stream encounters an error
|
||||
*
|
||||
* @remarks
|
||||
* This method handles the initialization of the VS Code LM client if not already created,
|
||||
* converts the messages to VS Code LM format, and streams the response chunks.
|
||||
* Tool calls handling is currently a work in progress.
|
||||
*/
|
||||
dispose(): void {
|
||||
if (this.disposable) {
|
||||
this.disposable.dispose()
|
||||
}
|
||||
|
||||
if (this.currentRequestCancellation) {
|
||||
this.currentRequestCancellation.cancel()
|
||||
this.currentRequestCancellation.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
private async countTokens(text: string | vscode.LanguageModelChatMessage): Promise<number> {
|
||||
// Check for required dependencies
|
||||
if (!this.client) {
|
||||
console.warn("Cline <Language Model API>: No client available for token counting")
|
||||
return 0
|
||||
}
|
||||
|
||||
if (!this.currentRequestCancellation) {
|
||||
console.warn("Cline <Language Model API>: No cancellation token available for token counting")
|
||||
return 0
|
||||
}
|
||||
|
||||
// Validate input
|
||||
if (!text) {
|
||||
console.debug("Cline <Language Model API>: Empty text provided for token counting")
|
||||
return 0
|
||||
}
|
||||
|
||||
try {
|
||||
// Handle different input types
|
||||
let tokenCount: number
|
||||
|
||||
if (typeof text === "string") {
|
||||
tokenCount = await this.client.countTokens(text, this.currentRequestCancellation.token)
|
||||
} else if (text instanceof vscode.LanguageModelChatMessage) {
|
||||
// For chat messages, ensure we have content
|
||||
if (!text.content || (Array.isArray(text.content) && text.content.length === 0)) {
|
||||
console.debug("Cline <Language Model API>: Empty chat message content")
|
||||
return 0
|
||||
}
|
||||
tokenCount = await this.client.countTokens(text, this.currentRequestCancellation.token)
|
||||
} else {
|
||||
console.warn("Cline <Language Model API>: Invalid input type for token counting")
|
||||
return 0
|
||||
}
|
||||
|
||||
// Validate the result
|
||||
if (typeof tokenCount !== "number") {
|
||||
console.warn("Cline <Language Model API>: Non-numeric token count received:", tokenCount)
|
||||
return 0
|
||||
}
|
||||
|
||||
if (tokenCount < 0) {
|
||||
console.warn("Cline <Language Model API>: Negative token count received:", tokenCount)
|
||||
return 0
|
||||
}
|
||||
|
||||
return tokenCount
|
||||
} catch (error) {
|
||||
// Handle specific error types
|
||||
if (error instanceof vscode.CancellationError) {
|
||||
console.debug("Cline <Language Model API>: Token counting cancelled by user")
|
||||
return 0
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : "Unknown error"
|
||||
console.warn("Cline <Language Model API>: Token counting failed:", errorMessage)
|
||||
|
||||
// Log additional error details if available
|
||||
if (error instanceof Error && error.stack) {
|
||||
console.debug("Token counting error stack:", error.stack)
|
||||
}
|
||||
|
||||
return 0 // Fallback to prevent stream interruption
|
||||
}
|
||||
}
|
||||
|
||||
private async calculateTotalInputTokens(
|
||||
systemPrompt: string,
|
||||
vsCodeLmMessages: vscode.LanguageModelChatMessage[],
|
||||
): Promise<number> {
|
||||
const systemTokens: number = await this.countTokens(systemPrompt)
|
||||
|
||||
const messageTokens: number[] = await Promise.all(vsCodeLmMessages.map((msg) => this.countTokens(msg)))
|
||||
|
||||
return systemTokens + messageTokens.reduce((sum: number, tokens: number): number => sum + tokens, 0)
|
||||
}
|
||||
|
||||
private ensureCleanState(): void {
|
||||
if (this.currentRequestCancellation) {
|
||||
this.currentRequestCancellation.cancel()
|
||||
this.currentRequestCancellation.dispose()
|
||||
this.currentRequestCancellation = null
|
||||
}
|
||||
}
|
||||
|
||||
private async getClient(): Promise<vscode.LanguageModelChat> {
|
||||
if (!this.client) {
|
||||
console.debug("Cline <Language Model API>: Getting client with options:", {
|
||||
vsCodeLmModelSelector: this.options.vsCodeLmModelSelector,
|
||||
hasOptions: !!this.options,
|
||||
selectorKeys: this.options.vsCodeLmModelSelector ? Object.keys(this.options.vsCodeLmModelSelector) : [],
|
||||
})
|
||||
|
||||
try {
|
||||
// Use default empty selector if none provided to get all available models
|
||||
const selector = this.options?.vsCodeLmModelSelector || {}
|
||||
console.debug("Cline <Language Model API>: Creating client with selector:", selector)
|
||||
this.client = await this.createClient(selector)
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Unknown error"
|
||||
console.error("Cline <Language Model API>: Client creation failed:", message)
|
||||
throw new Error(`Cline <Language Model API>: Failed to create client: ${message}`)
|
||||
}
|
||||
}
|
||||
|
||||
return this.client
|
||||
}
|
||||
|
||||
private cleanTerminalOutput(text: string): string {
|
||||
if (!text) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return (
|
||||
text
|
||||
// Normalize line breaks
|
||||
.replace(/\r\n/g, "\n")
|
||||
.replace(/\r/g, "\n")
|
||||
|
||||
// Remove ANSI escape sequences
|
||||
.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "") // Full set of ANSI sequences
|
||||
.replace(/\x9B[0-?]*[ -/]*[@-~]/g, "") // CSI sequences
|
||||
|
||||
// Remove terminal title setting sequences and other OSC sequences
|
||||
.replace(/\x1B\][0-9;]*(?:\x07|\x1B\\)/g, "")
|
||||
|
||||
// Remove control characters
|
||||
.replace(/[\x00-\x09\x0B-\x0C\x0E-\x1F\x7F]/g, "")
|
||||
|
||||
// Remove VS Code escape sequences
|
||||
.replace(/\x1B[PD].*?\x1B\\/g, "") // DCS sequences
|
||||
.replace(/\x1B_.*?\x1B\\/g, "") // APC sequences
|
||||
.replace(/\x1B\^.*?\x1B\\/g, "") // PM sequences
|
||||
.replace(/\x1B\[[\d;]*[HfABCDEFGJKST]/g, "") // Cursor movement and clear screen
|
||||
|
||||
// Remove Windows paths and service information
|
||||
.replace(/^(?:PS )?[A-Z]:\\[^\n]*$/gm, "")
|
||||
.replace(/^;?Cwd=.*$/gm, "")
|
||||
|
||||
// Clean escaped sequences
|
||||
.replace(/\\x[0-9a-fA-F]{2}/g, "")
|
||||
.replace(/\\u[0-9a-fA-F]{4}/g, "")
|
||||
|
||||
// Final cleanup
|
||||
.replace(/\n{3,}/g, "\n\n") // Remove multiple empty lines
|
||||
.trim()
|
||||
)
|
||||
}
|
||||
|
||||
private cleanMessageContent(content: any): any {
|
||||
if (!content) {
|
||||
return content
|
||||
}
|
||||
|
||||
if (typeof content === "string") {
|
||||
return this.cleanTerminalOutput(content)
|
||||
}
|
||||
|
||||
if (Array.isArray(content)) {
|
||||
return content.map((item) => this.cleanMessageContent(item))
|
||||
}
|
||||
|
||||
if (typeof content === "object") {
|
||||
const cleaned: any = {}
|
||||
for (const [key, value] of Object.entries(content)) {
|
||||
cleaned[key] = this.cleanMessageContent(value)
|
||||
}
|
||||
return cleaned
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
// Ensure clean state before starting a new request
|
||||
this.ensureCleanState()
|
||||
const client: vscode.LanguageModelChat = await this.getClient()
|
||||
|
||||
// Clean system prompt and messages
|
||||
const cleanedSystemPrompt = this.cleanTerminalOutput(systemPrompt)
|
||||
const cleanedMessages = messages.map((msg) => ({
|
||||
...msg,
|
||||
content: this.cleanMessageContent(msg.content),
|
||||
}))
|
||||
|
||||
// Convert Anthropic messages to VS Code LM messages
|
||||
const vsCodeLmMessages: vscode.LanguageModelChatMessage[] = [
|
||||
vscode.LanguageModelChatMessage.Assistant(cleanedSystemPrompt),
|
||||
...convertToVsCodeLmMessages(cleanedMessages),
|
||||
]
|
||||
|
||||
// Initialize cancellation token for the request
|
||||
this.currentRequestCancellation = new vscode.CancellationTokenSource()
|
||||
|
||||
// Calculate input tokens before starting the stream
|
||||
const totalInputTokens: number = await this.calculateTotalInputTokens(systemPrompt, vsCodeLmMessages)
|
||||
|
||||
// Accumulate the text and count at the end of the stream to reduce token counting overhead.
|
||||
let accumulatedText: string = ""
|
||||
|
||||
try {
|
||||
// Create the response stream with minimal required options
|
||||
const requestOptions: vscode.LanguageModelChatRequestOptions = {
|
||||
justification: `Cline would like to use '${client.name}' from '${client.vendor}', Click 'Allow' to proceed.`,
|
||||
}
|
||||
|
||||
// Note: Tool support is currently provided by the VSCode Language Model API directly
|
||||
// Extensions can register tools using vscode.lm.registerTool()
|
||||
|
||||
const response: vscode.LanguageModelChatResponse = await client.sendRequest(
|
||||
vsCodeLmMessages,
|
||||
requestOptions,
|
||||
this.currentRequestCancellation.token,
|
||||
)
|
||||
|
||||
// Consume the stream and handle both text and tool call chunks
|
||||
for await (const chunk of response.stream) {
|
||||
if (chunk instanceof vscode.LanguageModelTextPart) {
|
||||
// Validate text part value
|
||||
if (typeof chunk.value !== "string") {
|
||||
console.warn("Cline <Language Model API>: Invalid text part value received:", chunk.value)
|
||||
continue
|
||||
}
|
||||
|
||||
accumulatedText += chunk.value
|
||||
yield {
|
||||
type: "text",
|
||||
text: chunk.value,
|
||||
}
|
||||
} else if (chunk instanceof vscode.LanguageModelToolCallPart) {
|
||||
try {
|
||||
// Validate tool call parameters
|
||||
if (!chunk.name || typeof chunk.name !== "string") {
|
||||
console.warn("Cline <Language Model API>: Invalid tool name received:", chunk.name)
|
||||
continue
|
||||
}
|
||||
|
||||
if (!chunk.callId || typeof chunk.callId !== "string") {
|
||||
console.warn("Cline <Language Model API>: Invalid tool callId received:", chunk.callId)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure input is a valid object
|
||||
if (!chunk.input || typeof chunk.input !== "object") {
|
||||
console.warn("Cline <Language Model API>: Invalid tool input received:", chunk.input)
|
||||
continue
|
||||
}
|
||||
|
||||
// Convert tool calls to text format with proper error handling
|
||||
const toolCall = {
|
||||
type: "tool_call",
|
||||
name: chunk.name,
|
||||
arguments: chunk.input,
|
||||
callId: chunk.callId,
|
||||
}
|
||||
|
||||
const toolCallText = JSON.stringify(toolCall)
|
||||
accumulatedText += toolCallText
|
||||
|
||||
// Log tool call for debugging
|
||||
console.debug("Cline <Language Model API>: Processing tool call:", {
|
||||
name: chunk.name,
|
||||
callId: chunk.callId,
|
||||
inputSize: JSON.stringify(chunk.input).length,
|
||||
})
|
||||
|
||||
yield {
|
||||
type: "text",
|
||||
text: toolCallText,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Cline <Language Model API>: Failed to process tool call:", error)
|
||||
// Continue processing other chunks even if one fails
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
console.warn("Cline <Language Model API>: Unknown chunk type received:", chunk)
|
||||
}
|
||||
}
|
||||
|
||||
// Count tokens in the accumulated text after stream completion
|
||||
const totalOutputTokens: number = await this.countTokens(accumulatedText)
|
||||
|
||||
// Report final usage after stream completion
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: totalInputTokens,
|
||||
outputTokens: totalOutputTokens,
|
||||
totalCost: calculateApiCost(this.getModel().info, totalInputTokens, totalOutputTokens),
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
this.ensureCleanState()
|
||||
|
||||
if (error instanceof vscode.CancellationError) {
|
||||
throw new Error("Cline <Language Model API>: Request cancelled by user")
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
console.error("Cline <Language Model API>: Stream error details:", {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
name: error.name,
|
||||
})
|
||||
|
||||
// Return original error if it's already an Error instance
|
||||
throw error
|
||||
} else if (typeof error === "object" && error !== null) {
|
||||
// Handle error-like objects
|
||||
const errorDetails = JSON.stringify(error, null, 2)
|
||||
console.error("Cline <Language Model API>: Stream error object:", errorDetails)
|
||||
throw new Error(`Cline <Language Model API>: Response stream error: ${errorDetails}`)
|
||||
} else {
|
||||
// Fallback for unknown error types
|
||||
const errorMessage = String(error)
|
||||
console.error("Cline <Language Model API>: Unknown stream error:", errorMessage)
|
||||
throw new Error(`Cline <Language Model API>: Response stream error: ${errorMessage}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return model information based on the current client state
|
||||
getModel(): { id: string; info: ModelInfo } {
|
||||
if (this.client) {
|
||||
// Validate client properties
|
||||
const requiredProps = {
|
||||
id: this.client.id,
|
||||
vendor: this.client.vendor,
|
||||
family: this.client.family,
|
||||
version: this.client.version,
|
||||
maxInputTokens: this.client.maxInputTokens,
|
||||
}
|
||||
|
||||
// Log any missing properties for debugging
|
||||
for (const [prop, value] of Object.entries(requiredProps)) {
|
||||
if (!value && value !== 0) {
|
||||
console.warn(`Cline <Language Model API>: Client missing ${prop} property`)
|
||||
}
|
||||
}
|
||||
|
||||
// Construct model ID using available information
|
||||
const modelParts = [this.client.vendor, this.client.family, this.client.version].filter(Boolean)
|
||||
|
||||
const modelId = this.client.id || modelParts.join(SELECTOR_SEPARATOR)
|
||||
|
||||
// Build model info with conservative defaults for missing values
|
||||
const modelInfo: ModelInfo = {
|
||||
maxTokens: -1, // Unlimited tokens by default
|
||||
contextWindow:
|
||||
typeof this.client.maxInputTokens === "number"
|
||||
? Math.max(0, this.client.maxInputTokens)
|
||||
: openAiModelInfoSaneDefaults.contextWindow,
|
||||
supportsImages: false, // VSCode Language Model API currently doesn't support image inputs
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description: `VSCode Language Model: ${modelId}`,
|
||||
}
|
||||
|
||||
return { id: modelId, info: modelInfo }
|
||||
}
|
||||
|
||||
// Fallback when no client is available
|
||||
const fallbackId = this.options.vsCodeLmModelSelector
|
||||
? stringifyVsCodeLmModelSelector(this.options.vsCodeLmModelSelector)
|
||||
: "vscode-lm"
|
||||
|
||||
console.debug("Cline <Language Model API>: No client available, using fallback model info")
|
||||
|
||||
return {
|
||||
id: fallbackId,
|
||||
info: {
|
||||
...openAiModelInfoSaneDefaults,
|
||||
description: `VSCode Language Model (Fallback): ${fallbackId}`,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async completePrompt(prompt: string): Promise<string> {
|
||||
try {
|
||||
const client = await this.getClient()
|
||||
const response = await client.sendRequest(
|
||||
[vscode.LanguageModelChatMessage.User(prompt)],
|
||||
{},
|
||||
new vscode.CancellationTokenSource().token,
|
||||
)
|
||||
let result = ""
|
||||
for await (const chunk of response.stream) {
|
||||
if (chunk instanceof vscode.LanguageModelTextPart) {
|
||||
result += chunk.value
|
||||
}
|
||||
}
|
||||
return result
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
throw new Error(`VSCode LM completion error: ${error.message}`)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
200
src/api/transform/vscode-lm-format.ts
Normal file
200
src/api/transform/vscode-lm-format.ts
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
/**
|
||||
* Safely converts a value into a plain object.
|
||||
*/
|
||||
function asObjectSafe(value: any): object {
|
||||
// Handle null/undefined
|
||||
if (!value) {
|
||||
return {}
|
||||
}
|
||||
|
||||
try {
|
||||
// Handle strings that might be JSON
|
||||
if (typeof value === "string") {
|
||||
return JSON.parse(value)
|
||||
}
|
||||
|
||||
// Handle pre-existing objects
|
||||
if (typeof value === "object") {
|
||||
return Object.assign({}, value)
|
||||
}
|
||||
|
||||
return {}
|
||||
} catch (error) {
|
||||
console.warn("Cline <Language Model API>: Failed to parse object:", error)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
export function convertToVsCodeLmMessages(
|
||||
anthropicMessages: Anthropic.Messages.MessageParam[],
|
||||
): vscode.LanguageModelChatMessage[] {
|
||||
const vsCodeLmMessages: vscode.LanguageModelChatMessage[] = []
|
||||
|
||||
for (const anthropicMessage of anthropicMessages) {
|
||||
// Handle simple string messages
|
||||
if (typeof anthropicMessage.content === "string") {
|
||||
vsCodeLmMessages.push(
|
||||
anthropicMessage.role === "assistant"
|
||||
? vscode.LanguageModelChatMessage.Assistant(anthropicMessage.content)
|
||||
: vscode.LanguageModelChatMessage.User(anthropicMessage.content),
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle complex message structures
|
||||
switch (anthropicMessage.role) {
|
||||
case "user": {
|
||||
const { nonToolMessages, toolMessages } = anthropicMessage.content.reduce<{
|
||||
nonToolMessages: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[]
|
||||
toolMessages: Anthropic.ToolResultBlockParam[]
|
||||
}>(
|
||||
(acc, part) => {
|
||||
if (part.type === "tool_result") {
|
||||
acc.toolMessages.push(part)
|
||||
} else if (part.type === "text" || part.type === "image") {
|
||||
acc.nonToolMessages.push(part)
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{ nonToolMessages: [], toolMessages: [] },
|
||||
)
|
||||
|
||||
// Process tool messages first then non-tool messages
|
||||
const contentParts = [
|
||||
// Convert tool messages to ToolResultParts
|
||||
...toolMessages.map((toolMessage) => {
|
||||
// Process tool result content into TextParts
|
||||
const toolContentParts: vscode.LanguageModelTextPart[] =
|
||||
typeof toolMessage.content === "string"
|
||||
? [new vscode.LanguageModelTextPart(toolMessage.content)]
|
||||
: (toolMessage.content?.map((part) => {
|
||||
if (part.type === "image") {
|
||||
return new vscode.LanguageModelTextPart(
|
||||
`[Image (${part.source?.type || "Unknown source-type"}): ${part.source?.media_type || "unknown media-type"} not supported by VSCode LM API]`,
|
||||
)
|
||||
}
|
||||
return new vscode.LanguageModelTextPart(part.text)
|
||||
}) ?? [new vscode.LanguageModelTextPart("")])
|
||||
|
||||
return new vscode.LanguageModelToolResultPart(toolMessage.tool_use_id, toolContentParts)
|
||||
}),
|
||||
|
||||
// Convert non-tool messages to TextParts after tool messages
|
||||
...nonToolMessages.map((part) => {
|
||||
if (part.type === "image") {
|
||||
return new vscode.LanguageModelTextPart(
|
||||
`[Image (${part.source?.type || "Unknown source-type"}): ${part.source?.media_type || "unknown media-type"} not supported by VSCode LM API]`,
|
||||
)
|
||||
}
|
||||
return new vscode.LanguageModelTextPart(part.text)
|
||||
}),
|
||||
]
|
||||
|
||||
// Add single user message with all content parts
|
||||
vsCodeLmMessages.push(vscode.LanguageModelChatMessage.User(contentParts))
|
||||
break
|
||||
}
|
||||
|
||||
case "assistant": {
|
||||
const { nonToolMessages, toolMessages } = anthropicMessage.content.reduce<{
|
||||
nonToolMessages: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[]
|
||||
toolMessages: Anthropic.ToolUseBlockParam[]
|
||||
}>(
|
||||
(acc, part) => {
|
||||
if (part.type === "tool_use") {
|
||||
acc.toolMessages.push(part)
|
||||
} else if (part.type === "text" || part.type === "image") {
|
||||
acc.nonToolMessages.push(part)
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{ nonToolMessages: [], toolMessages: [] },
|
||||
)
|
||||
|
||||
// Process tool messages first then non-tool messages
|
||||
const contentParts = [
|
||||
// Convert tool messages to ToolCallParts first
|
||||
...toolMessages.map(
|
||||
(toolMessage) =>
|
||||
new vscode.LanguageModelToolCallPart(
|
||||
toolMessage.id,
|
||||
toolMessage.name,
|
||||
asObjectSafe(toolMessage.input),
|
||||
),
|
||||
),
|
||||
|
||||
// Convert non-tool messages to TextParts after tool messages
|
||||
...nonToolMessages.map((part) => {
|
||||
if (part.type === "image") {
|
||||
return new vscode.LanguageModelTextPart("[Image generation not supported by VSCode LM API]")
|
||||
}
|
||||
return new vscode.LanguageModelTextPart(part.text)
|
||||
}),
|
||||
]
|
||||
|
||||
// Add the assistant message to the list of messages
|
||||
vsCodeLmMessages.push(vscode.LanguageModelChatMessage.Assistant(contentParts))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vsCodeLmMessages
|
||||
}
|
||||
|
||||
export function convertToAnthropicRole(vsCodeLmMessageRole: vscode.LanguageModelChatMessageRole): string | null {
|
||||
switch (vsCodeLmMessageRole) {
|
||||
case vscode.LanguageModelChatMessageRole.Assistant:
|
||||
return "assistant"
|
||||
case vscode.LanguageModelChatMessageRole.User:
|
||||
return "user"
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function convertToAnthropicMessage(
|
||||
vsCodeLmMessage: vscode.LanguageModelChatMessage,
|
||||
): Promise<Anthropic.Messages.Message> {
|
||||
const anthropicRole: string | null = convertToAnthropicRole(vsCodeLmMessage.role)
|
||||
if (anthropicRole !== "assistant") {
|
||||
throw new Error("Cline <Language Model API>: Only assistant messages are supported.")
|
||||
}
|
||||
|
||||
return {
|
||||
id: crypto.randomUUID(),
|
||||
type: "message",
|
||||
model: "vscode-lm",
|
||||
role: anthropicRole,
|
||||
content: vsCodeLmMessage.content
|
||||
.map((part): Anthropic.ContentBlock | null => {
|
||||
if (part instanceof vscode.LanguageModelTextPart) {
|
||||
return {
|
||||
type: "text",
|
||||
text: part.value,
|
||||
}
|
||||
}
|
||||
|
||||
if (part instanceof vscode.LanguageModelToolCallPart) {
|
||||
return {
|
||||
type: "tool_use",
|
||||
id: part.callId || crypto.randomUUID(),
|
||||
name: part.name,
|
||||
input: asObjectSafe(part.input),
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
.filter((part): part is Anthropic.ContentBlock => part !== null),
|
||||
stop_reason: null,
|
||||
stop_sequence: null,
|
||||
usage: {
|
||||
input_tokens: 0,
|
||||
output_tokens: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -69,6 +69,7 @@ type GlobalStateKey =
|
|||
| "autoApprovalSettings"
|
||||
| "browserSettings"
|
||||
| "chatSettings"
|
||||
| "vsCodeLmModelSelector"
|
||||
|
||||
export const GlobalFileNames = {
|
||||
apiConversationHistory: "api_conversation_history.json",
|
||||
|
|
@ -424,6 +425,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
openRouterModelInfo,
|
||||
openRouterAdvisorModelId,
|
||||
openRouterAdvisorModelInfo,
|
||||
vsCodeLmModelSelector,
|
||||
} = message.apiConfiguration
|
||||
await this.updateGlobalState("apiProvider", apiProvider)
|
||||
await this.updateGlobalState("apiModelId", apiModelId)
|
||||
|
|
@ -454,6 +456,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
await this.updateGlobalState("openRouterModelInfo", openRouterModelInfo)
|
||||
await this.updateGlobalState("openRouterAdvisorModelId", openRouterAdvisorModelId)
|
||||
await this.updateGlobalState("openRouterAdvisorModelInfo", openRouterAdvisorModelInfo)
|
||||
await this.updateGlobalState("vsCodeLmModelSelector", vsCodeLmModelSelector)
|
||||
if (this.cline) {
|
||||
this.cline.api = buildApiHandler(message.apiConfiguration)
|
||||
}
|
||||
|
|
@ -547,6 +550,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
lmStudioModels,
|
||||
})
|
||||
break
|
||||
case "requestVsCodeLmModels":
|
||||
const vsCodeLmModels = await this.getVsCodeLmModels()
|
||||
this.postMessageToWebview({ type: "vsCodeLmModels", vsCodeLmModels })
|
||||
break
|
||||
case "refreshOpenRouterModels":
|
||||
await this.refreshOpenRouterModels()
|
||||
break
|
||||
|
|
@ -674,6 +681,18 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
return settingsDir
|
||||
}
|
||||
|
||||
// VSCode LM API
|
||||
|
||||
private async getVsCodeLmModels() {
|
||||
try {
|
||||
const models = await vscode.lm.selectChatModels({})
|
||||
return models || []
|
||||
} catch (error) {
|
||||
console.error("Error fetching VS Code LM models:", error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// Ollama
|
||||
|
||||
async getOllamaModels(baseUrl?: string) {
|
||||
|
|
@ -1090,6 +1109,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
autoApprovalSettings,
|
||||
browserSettings,
|
||||
chatSettings,
|
||||
vsCodeLmModelSelector,
|
||||
] = await Promise.all([
|
||||
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
|
||||
this.getGlobalState("apiModelId") as Promise<string | undefined>,
|
||||
|
|
@ -1126,6 +1146,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
this.getGlobalState("autoApprovalSettings") as Promise<AutoApprovalSettings | undefined>,
|
||||
this.getGlobalState("browserSettings") as Promise<BrowserSettings | undefined>,
|
||||
this.getGlobalState("chatSettings") as Promise<ChatSettings | undefined>,
|
||||
this.getGlobalState("vsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
|
||||
])
|
||||
|
||||
let apiProvider: ApiProvider
|
||||
|
|
@ -1173,6 +1194,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
openRouterModelInfo,
|
||||
openRouterAdvisorModelId,
|
||||
openRouterAdvisorModelInfo,
|
||||
vsCodeLmModelSelector,
|
||||
},
|
||||
lastShownAnnouncementId,
|
||||
customInstructions,
|
||||
|
|
|
|||
|
|
@ -71,14 +71,14 @@ This approach allows us to leverage advanced features when available while ensur
|
|||
*/
|
||||
declare module "vscode" {
|
||||
// https://github.com/microsoft/vscode/blob/f0417069c62e20f3667506f4b7e53ca0004b4e3e/src/vscode-dts/vscode.d.ts#L7442
|
||||
interface Terminal {
|
||||
shellIntegration?: {
|
||||
cwd?: vscode.Uri
|
||||
executeCommand?: (command: string) => {
|
||||
read: () => AsyncIterable<string>
|
||||
}
|
||||
}
|
||||
}
|
||||
// interface Terminal {
|
||||
// shellIntegration?: {
|
||||
// cwd?: vscode.Uri
|
||||
// executeCommand?: (command: string) => {
|
||||
// read: () => AsyncIterable<string>
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// https://github.com/microsoft/vscode/blob/f0417069c62e20f3667506f4b7e53ca0004b4e3e/src/vscode-dts/vscode.d.ts#L10794
|
||||
interface Window {
|
||||
onDidStartTerminalShellExecution?: (
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ export interface ExtensionMessage {
|
|||
| "mcpServers"
|
||||
| "relinquishControl"
|
||||
| "openAdvisorModelSettings"
|
||||
| "vsCodeLmModels"
|
||||
| "requestVsCodeLmModels"
|
||||
text?: string
|
||||
action?: "chatButtonClicked" | "mcpButtonClicked" | "settingsButtonClicked" | "historyButtonClicked" | "didBecomeVisible"
|
||||
invoke?: "sendMessage" | "primaryButtonClick" | "secondaryButtonClick"
|
||||
|
|
@ -30,6 +32,7 @@ export interface ExtensionMessage {
|
|||
images?: string[]
|
||||
ollamaModels?: string[]
|
||||
lmStudioModels?: string[]
|
||||
vsCodeLmModels?: { vendor?: string; family?: string; version?: string; id?: string }[]
|
||||
filePaths?: string[]
|
||||
partialMessage?: ClineMessage
|
||||
openRouterModels?: Record<string, ModelInfo>
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export interface WebviewMessage {
|
|||
| "checkpointRestore"
|
||||
| "taskCompletionViewChanges"
|
||||
| "openAdvisorModelSettings"
|
||||
| "requestVsCodeLmModels"
|
||||
// | "relaunchChromeDebugMode"
|
||||
text?: string
|
||||
askResponse?: ClineAskResponse
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export type ApiProvider =
|
|||
| "openai-native"
|
||||
| "deepseek"
|
||||
| "mistral"
|
||||
| "vscode-lm"
|
||||
|
||||
export interface ApiHandlerOptions {
|
||||
apiModelId?: string
|
||||
|
|
@ -40,6 +41,7 @@ export interface ApiHandlerOptions {
|
|||
deepSeekApiKey?: string
|
||||
mistralApiKey?: string
|
||||
azureApiVersion?: string
|
||||
vsCodeLmModelSelector?: any
|
||||
}
|
||||
|
||||
export type ApiConfiguration = ApiHandlerOptions & {
|
||||
|
|
|
|||
7
src/shared/vsCodeSelectorUtils.ts
Normal file
7
src/shared/vsCodeSelectorUtils.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { LanguageModelChatSelector } from "vscode"
|
||||
|
||||
export const SELECTOR_SEPARATOR = "/"
|
||||
|
||||
export function stringifyVsCodeLmModelSelector(selector: LanguageModelChatSelector): string {
|
||||
return [selector.vendor, selector.family, selector.version, selector.id].filter(Boolean).join(SELECTOR_SEPARATOR)
|
||||
}
|
||||
|
|
@ -96,6 +96,7 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
|
|||
const isCostAvailable = useMemo(() => {
|
||||
return (
|
||||
apiConfiguration?.apiProvider !== "openai" &&
|
||||
apiConfiguration?.apiProvider !== "vscode-lm" &&
|
||||
apiConfiguration?.apiProvider !== "ollama" &&
|
||||
apiConfiguration?.apiProvider !== "lmstudio" &&
|
||||
apiConfiguration?.apiProvider !== "gemini"
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import { vscode } from "../../utils/vscode"
|
|||
import VSCodeButtonLink from "../common/VSCodeButtonLink"
|
||||
import OpenRouterModelPicker, { ModelDescriptionMarkdown, OPENROUTER_MODEL_PICKER_Z_INDEX } from "./OpenRouterModelPicker"
|
||||
import styled from "styled-components"
|
||||
import * as vscodemodels from "vscode"
|
||||
|
||||
interface ApiOptionsProps {
|
||||
showModelOptions: boolean
|
||||
|
|
@ -97,6 +98,7 @@ const ApiOptions = ({
|
|||
const { apiConfiguration, setApiConfiguration, uriScheme } = useExtensionState()
|
||||
const [ollamaModels, setOllamaModels] = useState<string[]>([])
|
||||
const [lmStudioModels, setLmStudioModels] = useState<string[]>([])
|
||||
const [vsCodeLmModels, setVsCodeLmModels] = useState<vscodemodels.LanguageModelChatSelector[]>([])
|
||||
const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl)
|
||||
const [azureApiVersionSelected, setAzureApiVersionSelected] = useState(!!apiConfiguration?.azureApiVersion)
|
||||
const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false)
|
||||
|
|
@ -125,14 +127,19 @@ const ApiOptions = ({
|
|||
type: "requestLmStudioModels",
|
||||
text: apiConfiguration?.lmStudioBaseUrl,
|
||||
})
|
||||
} else if (selectedProvider === "vscode-lm") {
|
||||
vscode.postMessage({ type: "requestVsCodeLmModels" })
|
||||
}
|
||||
}, [selectedProvider, apiConfiguration?.ollamaBaseUrl, apiConfiguration?.lmStudioBaseUrl])
|
||||
useEffect(() => {
|
||||
if (selectedProvider === "ollama" || selectedProvider === "lmstudio") {
|
||||
if (selectedProvider === "ollama" || selectedProvider === "lmstudio" || selectedProvider === "vscode-lm") {
|
||||
requestLocalModels()
|
||||
}
|
||||
}, [selectedProvider, requestLocalModels])
|
||||
useInterval(requestLocalModels, selectedProvider === "ollama" || selectedProvider === "lmstudio" ? 2000 : null)
|
||||
useInterval(
|
||||
requestLocalModels,
|
||||
selectedProvider === "ollama" || selectedProvider === "lmstudio" || selectedProvider === "vscode-lm" ? 2000 : null,
|
||||
)
|
||||
|
||||
const handleMessage = useCallback((event: MessageEvent) => {
|
||||
const message: ExtensionMessage = event.data
|
||||
|
|
@ -140,6 +147,8 @@ const ApiOptions = ({
|
|||
setOllamaModels(message.ollamaModels)
|
||||
} else if (message.type === "lmStudioModels" && message.lmStudioModels) {
|
||||
setLmStudioModels(message.lmStudioModels)
|
||||
} else if (message.type === "vsCodeLmModels" && message.vsCodeLmModels) {
|
||||
setVsCodeLmModels(message.vsCodeLmModels)
|
||||
}
|
||||
}, [])
|
||||
useEvent("message", handleMessage)
|
||||
|
|
@ -204,6 +213,7 @@ const ApiOptions = ({
|
|||
<VSCodeOption value="bedrock">AWS Bedrock</VSCodeOption>
|
||||
<VSCodeOption value="openai-native">OpenAI</VSCodeOption>
|
||||
<VSCodeOption value="openai">OpenAI Compatible</VSCodeOption>
|
||||
<VSCodeOption value="vscode-lm">VS Code LM API</VSCodeOption>
|
||||
<VSCodeOption value="lmstudio">LM Studio</VSCodeOption>
|
||||
<VSCodeOption value="ollama">Ollama</VSCodeOption>
|
||||
</VSCodeDropdown>
|
||||
|
|
@ -630,6 +640,68 @@ const ApiOptions = ({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{selectedProvider === "vscode-lm" && (
|
||||
<div>
|
||||
<div className="dropdown-container">
|
||||
<label htmlFor="vscode-lm-model">
|
||||
<span style={{ fontWeight: 500 }}>Language Model</span>
|
||||
</label>
|
||||
{vsCodeLmModels.length > 0 ? (
|
||||
<VSCodeDropdown
|
||||
id="vscode-lm-model"
|
||||
value={
|
||||
apiConfiguration?.vsCodeLmModelSelector
|
||||
? `${apiConfiguration.vsCodeLmModelSelector.vendor ?? ""}/${apiConfiguration.vsCodeLmModelSelector.family ?? ""}`
|
||||
: ""
|
||||
}
|
||||
onChange={(e) => {
|
||||
const value = (e.target as HTMLInputElement).value
|
||||
if (!value) {
|
||||
return
|
||||
}
|
||||
const [vendor, family] = value.split("/")
|
||||
handleInputChange("vsCodeLmModelSelector")({
|
||||
target: {
|
||||
value: { vendor, family },
|
||||
},
|
||||
})
|
||||
}}
|
||||
style={{ width: "100%" }}>
|
||||
<VSCodeOption value="">Select a model...</VSCodeOption>
|
||||
{vsCodeLmModels.map((model) => (
|
||||
<VSCodeOption
|
||||
key={`${model.vendor}/${model.family}`}
|
||||
value={`${model.vendor}/${model.family}`}>
|
||||
{model.vendor} - {model.family}
|
||||
</VSCodeOption>
|
||||
))}
|
||||
</VSCodeDropdown>
|
||||
) : (
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
The VS Code Language Model API allows you to run models provided by other VS Code extensions
|
||||
(including but not limited to GitHub Copilot). The easiest way to get started is to install the
|
||||
Copilot extension from the VS Marketplace and enabling Claude 3.5 Sonnet.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-errorForeground)",
|
||||
fontWeight: 500,
|
||||
}}>
|
||||
Note: This is a very experimental integration and may not work as expected.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedProvider === "lmstudio" && (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
|
|
@ -773,6 +845,7 @@ const ApiOptions = ({
|
|||
selectedProvider !== "openai" &&
|
||||
selectedProvider !== "ollama" &&
|
||||
selectedProvider !== "lmstudio" &&
|
||||
selectedProvider !== "vscode-lm" &&
|
||||
showModelOptions && (
|
||||
<>
|
||||
<div className="dropdown-container">
|
||||
|
|
@ -1089,6 +1162,17 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration):
|
|||
selectedModelId: apiConfiguration?.lmStudioModelId || "",
|
||||
selectedModelInfo: openAiModelInfoSaneDefaults,
|
||||
}
|
||||
case "vscode-lm":
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
selectedModelId: apiConfiguration?.vsCodeLmModelSelector
|
||||
? `${apiConfiguration.vsCodeLmModelSelector.vendor}/${apiConfiguration.vsCodeLmModelSelector.family}`
|
||||
: "",
|
||||
selectedModelInfo: {
|
||||
...openAiModelInfoSaneDefaults,
|
||||
supportsImages: false, // VSCode LM API currently doesn't support images
|
||||
},
|
||||
}
|
||||
default:
|
||||
return getProviderData(anthropicModels, anthropicDefaultModelId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
|||
config.openAiNativeApiKey,
|
||||
config.deepSeekApiKey,
|
||||
config.mistralApiKey,
|
||||
config.vsCodeLmModelSelector,
|
||||
].some((key) => key !== undefined)
|
||||
: false
|
||||
setShowWelcome(!hasKey)
|
||||
|
|
|
|||
|
|
@ -58,6 +58,11 @@ export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): s
|
|||
return "You must provide a valid model ID."
|
||||
}
|
||||
break
|
||||
case "vscode-lm":
|
||||
if (!apiConfiguration.vsCodeLmModelSelector) {
|
||||
return "You must provide a valid model selector."
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue