Skip to content

Commit c0d673e

Browse files
committed
feat: add vscode lm copilot models support
1 parent 7f00577 commit c0d673e

File tree

18 files changed

+366
-192
lines changed

18 files changed

+366
-192
lines changed

.vscode/launch.json

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,31 @@
1010
"type": "extensionHost",
1111
"request": "launch",
1212
"runtimeExecutable": "${execPath}",
13+
"args": [
14+
"--extensionDevelopmentPath=${workspaceFolder}"
15+
// "--disable-extensions"
16+
],
17+
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
18+
"preLaunchTask": "npm: watch:hmr",
19+
"env": {
20+
// "NODE_DEBUG": "http"
21+
}
22+
},
23+
{
24+
"name": "HMR-Extension-UpdatePkgJson",
25+
"type": "extensionHost",
26+
"request": "launch",
27+
"runtimeExecutable": "${execPath}",
1328
"args": [
1429
"--extensionDevelopmentPath=${workspaceFolder}",
1530
"--disable-extensions"
1631
],
1732
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
18-
"preLaunchTask": "npm: watch:hmr",
33+
"preLaunchTask": "npm: watch:hmr-update-pkg-json",
1934
"env": {
2035
// "NODE_DEBUG": "http"
2136
}
2237
},
23-
// {
24-
// "name": "HMR-Extension-UpdatePkgJson",
25-
// "type": "extensionHost",
26-
// "request": "launch",
27-
// "runtimeExecutable": "${execPath}",
28-
// "args": [
29-
// "--extensionDevelopmentPath=${workspaceFolder}",
30-
// "--disable-extensions"
31-
// ],
32-
// "outFiles": ["${workspaceFolder}/dist/**/*.js"],
33-
// "preLaunchTask": "npm: watch:hmr:update-pkg-json",
34-
// "env": {
35-
// // "NODE_DEBUG": "http"
36-
// }
37-
// },
3838
{
3939
"name": "Extension",
4040
"type": "extensionHost",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@
338338
"test": "tsc --noEmit &&vitest --run",
339339
"watch": "vite build --watch",
340340
"watch:hmr": "vite",
341-
"watch:hmr:update-pkg-json": "cross-env UPDATE_PKG_JSON=enable vite",
341+
"watch:hmr-update-pkg-json": "cross-env UPDATE_PKG_JSON=enable vite",
342342
"generate-nls": "tsx ./scripts/generate-nls.ts"
343343
},
344344
"devDependencies": {

src/extension/actions/apply-actions.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { vfs } from '@extension/file-utils/vfs'
33
import { CodeEditRegister } from '@extension/registers/code-edit-register'
44
import { CodeEditTaskEntity } from '@extension/registers/code-edit-register/task-entity'
55
import type {
6+
CodeEditDiffMode,
67
CodeEditTask,
78
CodeEditTaskJson
89
} from '@extension/registers/code-edit-register/types'
@@ -26,6 +27,7 @@ type CreateTaskParams = {
2627
selectionRange?: vscode.Range
2728
code?: string
2829
cleanLast?: boolean
30+
diffMode?: CodeEditDiffMode
2931
}
3032

3133
type StartTaskParams = {
@@ -122,7 +124,8 @@ Don't reply with anything except the code.
122124
code,
123125
sessionId,
124126
conversationId,
125-
agentId
127+
agentId,
128+
diffMode
126129
} = context.actionParams
127130
this.validateProvider()
128131
this.validateSchemeUri(schemeUri)
@@ -150,7 +153,8 @@ Don't reply with anything except the code.
150153
selection: finalSelectionRange,
151154
isNewFile,
152155
newContent: code || '',
153-
abortController: new AbortController()
156+
abortController: new AbortController(),
157+
diffMode
154158
})
155159
)
156160
}
@@ -192,7 +196,8 @@ Don't reply with anything except the code.
192196
schemeUri,
193197
selectionRange,
194198
code,
195-
cleanLast
199+
cleanLast,
200+
diffMode
196201
} = context.actionParams
197202

198203
const task = await this.createApplyCodeTask({
@@ -204,7 +209,8 @@ Don't reply with anything except the code.
204209
schemeUri,
205210
selectionRange,
206211
code,
207-
cleanLast
212+
cleanLast,
213+
diffMode
208214
}
209215
})
210216

src/extension/actions/webvm-actions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ export class WebVMActionsCollection extends ServerActionCollection {
106106
webviewId,
107107
actionParams: {
108108
path: webviewState.initRouterPath,
109-
replace: true
109+
replace: true,
110+
refreshChatSessions: true
110111
}
111112
})
112113
}

src/extension/ai/model-providers/helpers/langchain-vscode/chat-models.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -277,20 +277,33 @@ export class ChatVSCode
277277
const vscodeAIMessage =
278278
vscode.LanguageModelChatMessage.Assistant(contentParts)
279279
const outputText = getTextFromVSCodeMessageContents(contentParts)
280+
const currentText = getTextFromVSCodeMessageContents([chunk])
280281

281282
usageMetadata.input_tokens = inputText.length
282283
usageMetadata.output_tokens = outputText.length
283284
usageMetadata.total_tokens =
284285
usageMetadata.input_tokens + usageMetadata.output_tokens
285286

286-
yield new ChatGenerationChunk({
287-
text: outputText,
288-
message: convertVSCodeOutputChunkMessageToLangChain(vscodeAIMessage, {
289-
responseMetadata: rest,
290-
usageMetadata
291-
})
287+
const generationChunk = new ChatGenerationChunk({
288+
text: currentText,
289+
message: convertVSCodeOutputChunkMessageToLangChain(
290+
vscodeAIMessage,
291+
chunk,
292+
{
293+
responseMetadata: rest,
294+
usageMetadata
295+
}
296+
)
292297
})
293-
await runManager?.handleLLMNewToken(outputText)
298+
yield generationChunk
299+
await runManager?.handleLLMNewToken(
300+
generationChunk.text ?? '',
301+
undefined,
302+
undefined,
303+
undefined,
304+
undefined,
305+
{ chunk: generationChunk }
306+
)
294307
}
295308

296309
// Yield the `response_metadata` as the final chunk.

src/extension/ai/model-providers/helpers/langchain-vscode/utils.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import {
77
HumanMessage,
88
SystemMessage,
99
ToolMessage,
10-
UsageMetadata
10+
UsageMetadata,
11+
type MessageContentImageUrl,
12+
type MessageContentText
1113
} from '@langchain/core/messages'
1214
import type { ToolCallChunk } from '@langchain/core/messages/tool'
1315
import { isLangChainTool } from '@langchain/core/utils/function_calling'
@@ -22,8 +24,31 @@ import type {
2224
VSCodeUserMessageContent
2325
} from './types'
2426

27+
export const convertVScodeMessageContentToLangchainMessageContent = (
28+
contents: (VSCodeMessageContent | unknown)[]
29+
) => {
30+
const langchainContents: (MessageContentText | MessageContentImageUrl)[] = []
31+
32+
contents.forEach(c => {
33+
if (c instanceof vscode.LanguageModelTextPart) {
34+
langchainContents.push({
35+
type: 'text',
36+
text: c.value
37+
})
38+
}
39+
if (c instanceof vscode.LanguageModelToolResultPart) {
40+
langchainContents.push(
41+
...convertVScodeMessageContentToLangchainMessageContent(c.content)
42+
)
43+
}
44+
})
45+
46+
return langchainContents
47+
}
48+
2549
export const convertVSCodeOutputChunkMessageToLangChain = (
2650
messages: vscode.LanguageModelChatMessage,
51+
vscodeMessageContent: VSCodeMessageContent,
2752
extra?: {
2853
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2954
responseMetadata?: Record<string, any>
@@ -38,8 +63,11 @@ export const convertVSCodeOutputChunkMessageToLangChain = (
3863
}
3964
})
4065

66+
const langchainContents =
67+
convertVScodeMessageContentToLangchainMessageContent([vscodeMessageContent])
68+
4169
return new AIMessageChunk({
42-
content: messages.content ?? '',
70+
content: langchainContents,
4371
tool_call_chunks: toolCallParts
4472
.map(tc => ({
4573
name: tc.name,
@@ -105,8 +133,14 @@ const convertHumanGenericMessagesToVSCodeMessage = (
105133
const vscodeMessageContents: VSCodeUserMessageContent[] = []
106134

107135
message.content.forEach(c => {
136+
if (typeof c === 'string') {
137+
vscodeMessageContents.push(new vscode.LanguageModelTextPart(c))
138+
return
139+
}
140+
108141
if (c.type === 'text') {
109142
vscodeMessageContents.push(new vscode.LanguageModelTextPart(c.text))
143+
return
110144
}
111145
if (c.type === 'image_url') {
112146
if (typeof c.image_url === 'string') {
@@ -117,6 +151,8 @@ const convertHumanGenericMessagesToVSCodeMessage = (
117151
// TODO: vscode current not support image
118152
// const imgUrl = c.image_url.url
119153
}
154+
155+
return
120156
}
121157
throw new Error(`Unsupported content type: ${c.type}`)
122158
})

src/extension/ai/model-providers/vscodelm.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ export class VSCodeLMModelProvider extends BaseModelProvider<
3232
async getSupportModelNames() {
3333
const models = await vscode.lm.selectChatModels()
3434

35-
return models.map(model => model.name)
35+
return models.map(model => model.id)
3636
}
3737
}

src/extension/registers/code-edit-register/task-entity.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ import { t, type TFunction } from 'i18next'
33
import { v4 as uuidv4 } from 'uuid'
44
import * as vscode from 'vscode'
55

6-
import { CodeEditTask, CodeEditTaskJson, CodeEditTaskState } from './types'
6+
import {
7+
CodeEditDiffMode,
8+
CodeEditTask,
9+
CodeEditTaskJson,
10+
CodeEditTaskState
11+
} from './types'
712

813
export class CodeEditTaskEntity extends BaseEntity<CodeEditTask> {
914
protected getDefaults(
@@ -16,6 +21,7 @@ export class CodeEditTaskEntity extends BaseEntity<CodeEditTask> {
1621
conversationId: uuidv4(),
1722
agentId: uuidv4(),
1823
state: CodeEditTaskState.Initial,
24+
diffMode: CodeEditDiffMode.TempFileDiff,
1925
fileUri: vscode.Uri.file(''),
2026
isNewFile: false,
2127
selectionRange: new vscode.Range(0, 0, 0, 0),

src/extension/registers/code-edit-register/task-manager.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import * as vscode from 'vscode'
1010

1111
import { CodeEditTaskEntity } from './task-entity'
1212
import {
13+
CodeEditDiffMode,
1314
CodeEditTask,
1415
CodeEditTaskState,
1516
type CodeEditTaskJson,
@@ -36,6 +37,7 @@ export class CodeEditTaskManager implements vscode.Disposable {
3637

3738
async createTask(params: CreateTaskParams): Promise<CodeEditTask> {
3839
const {
40+
diffMode,
3941
sessionId,
4042
conversationId,
4143
agentId,
@@ -51,6 +53,7 @@ export class CodeEditTaskManager implements vscode.Disposable {
5153
conversationId,
5254
agentId,
5355
state: CodeEditTaskState.Initial,
56+
diffMode: diffMode || CodeEditDiffMode.TempFileDiff,
5457
fileUri,
5558
isNewFile,
5659
selectionRange: selection,
@@ -110,7 +113,29 @@ export class CodeEditTaskManager implements vscode.Disposable {
110113
return existingDirs
111114
}
112115

113-
async showDiffView(task: CodeEditTask): Promise<WriteTmpFileResult> {
116+
private async ensureFileIsOpenAndActive(fileUri: vscode.Uri) {
117+
const document = await vscode.workspace.openTextDocument(fileUri)
118+
await vscode.window.showTextDocument(document)
119+
}
120+
121+
async showDiffView(task: CodeEditTask): Promise<{
122+
writeText: (text: string) => Promise<void>
123+
}> {
124+
if (task.diffMode === CodeEditDiffMode.ClipboardDiff) {
125+
await this.ensureFileIsOpenAndActive(task.fileUri)
126+
await vscode.env.clipboard.writeText(task.newContent)
127+
await vscode.commands.executeCommand(
128+
'workbench.files.action.compareWithClipboard',
129+
task.fileUri
130+
)
131+
132+
return {
133+
writeText: async (text: string) => {
134+
await vscode.env.clipboard.writeText(text)
135+
}
136+
}
137+
}
138+
114139
const diffWriter = await this.createTempDiffFile(task)
115140
this.tempFiles.set(task.id, diffWriter.tmpFileUri)
116141

src/extension/registers/code-edit-register/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@ export enum CodeEditTaskState {
99
Error = 'Error'
1010
}
1111

12+
export enum CodeEditDiffMode {
13+
ClipboardDiff = 'ClipboardDiff',
14+
TempFileDiff = 'TempFileDiff'
15+
}
16+
1217
// Represents a code edit task
1318
export interface CodeEditTask {
1419
id: string // task id = sessionId-conversationId-agentId-fileUri
1520
sessionId: string
1621
conversationId: string
1722
agentId: string
1823
state: CodeEditTaskState
24+
diffMode?: CodeEditDiffMode
1925

2026
// File info
2127
fileUri: Uri
@@ -45,6 +51,7 @@ export type CodeEditTaskJson = Omit<
4551
}
4652

4753
export type CreateTaskParams = {
54+
diffMode?: CodeEditDiffMode
4855
sessionId: string
4956
conversationId: string
5057
agentId: string

0 commit comments

Comments
 (0)