2023-11-13 16:30:27 +01:00
import * as vscode from 'vscode' ;
2024-02-23 15:56:39 +01:00
const CAT_NAMES_COMMAND_ID = 'cat.namesInEditor' ;
2024-03-20 20:44:27 -03:00
const CAT_PARTICIPANT_ID = 'chat-sample.cat' ;
2023-11-13 16:30:27 +01:00
2024-02-19 22:25:49 +00:00
interface ICatChatResult extends vscode . ChatResult {
2024-02-11 23:05:52 -03:00
metadata : {
command : string ;
}
2023-11-13 15:59:54 -06:00
}
2024-05-15 23:36:25 +02:00
const MODEL_SELECTOR : vscode.LanguageModelChatSelector = { vendor : 'copilot' , family : 'gpt-3.5-turbo' } ;
2024-02-11 23:05:52 -03:00
2023-11-13 16:30:27 +01:00
export function activate ( context : vscode.ExtensionContext ) {
2024-02-22 15:51:21 +01:00
// Define a Cat chat handler.
2024-02-19 22:25:49 +00:00
const handler : vscode.ChatRequestHandler = async ( request : vscode.ChatRequest , context : vscode.ChatContext , stream : vscode.ChatResponseStream , token : vscode.CancellationToken ) : Promise < ICatChatResult > = > {
2024-01-18 20:00:07 -03:00
// To talk to an LLM in your subcommand handler implementation, your
2023-11-13 16:30:27 +01:00
// extension can use VS Code's `requestChatAccess` API to access the Copilot API.
2023-11-13 15:59:54 -06:00
// The GitHub Copilot Chat extension implements this provider.
2024-02-11 23:05:52 -03:00
if ( request . command == 'teach' ) {
2024-02-28 18:19:09 +01:00
stream . progress ( 'Picking the right topic to teach...' ) ;
2024-02-23 13:09:10 +01:00
const topic = getTopic ( context . history ) ;
2023-11-13 16:30:27 +01:00
const messages = [
2024-05-15 23:36:25 +02:00
vscode . LanguageModelChatMessage . User ( 'You are a cat! Your job is to explain computer science concepts in the funny manner of a cat. Always start your response by stating what concept you are explaining. Always include code samples.' ) ,
vscode . LanguageModelChatMessage . User ( topic )
2023-11-13 16:30:27 +01:00
] ;
2024-05-15 23:36:25 +02:00
const [ model ] = await vscode . lm . selectChatModels ( MODEL_SELECTOR ) ;
const chatResponse = await model . sendRequest ( messages , { } , token ) ;
for await ( const fragment of chatResponse . text ) {
2024-02-11 23:05:52 -03:00
stream . markdown ( fragment ) ;
2023-11-13 16:30:27 +01:00
}
2024-02-11 23:05:52 -03:00
stream . button ( {
2024-02-23 15:56:39 +01:00
command : CAT_NAMES_COMMAND_ID ,
title : vscode.l10n.t ( 'Use Cat Names in Editor' )
2024-02-11 23:05:52 -03:00
} ) ;
return { metadata : { command : 'teach' } } ;
} else if ( request . command == 'play' ) {
2024-02-28 18:19:09 +01:00
stream . progress ( 'Throwing away the computer science books and preparing to play with some Python code...' ) ;
2023-11-13 16:30:27 +01:00
const messages = [
2024-05-15 23:36:25 +02:00
vscode . LanguageModelChatMessage . User ( 'You are a cat! Reply in the voice of a cat, using cat analogies when appropriate. Be concise to prepare for cat play time.' ) ,
vscode . LanguageModelChatMessage . User ( 'Give a small random python code samples (that have cat names for variables). ' + request . prompt )
2023-11-13 16:30:27 +01:00
] ;
2024-05-15 23:36:25 +02:00
const [ model ] = await vscode . lm . selectChatModels ( MODEL_SELECTOR ) ;
const chatResponse = await model . sendRequest ( messages , { } , token ) ;
for await ( const fragment of chatResponse . text ) {
2024-02-11 23:05:52 -03:00
stream . markdown ( fragment ) ;
2023-11-13 16:30:27 +01:00
}
2024-02-11 23:05:52 -03:00
return { metadata : { command : 'play' } } ;
2023-11-13 15:59:54 -06:00
} else {
2024-02-11 23:05:52 -03:00
const messages = [
2024-05-15 23:36:25 +02:00
vscode . LanguageModelChatMessage . User ( ` You are a cat! Think carefully and step by step like a cat would.
2024-05-03 16:22:40 +02:00
Your job is to explain computer science concepts in the funny manner of a cat , using cat metaphors . Always start your response by stating what concept you are explaining . Always include code samples . ` ),
2024-05-15 23:36:25 +02:00
vscode . LanguageModelChatMessage . User ( request . prompt )
2024-02-11 23:05:52 -03:00
] ;
2024-05-15 23:36:25 +02:00
const [ model ] = await vscode . lm . selectChatModels ( MODEL_SELECTOR ) ;
const chatResponse = await model . sendRequest ( messages , { } , token ) ;
for await ( const fragment of chatResponse . text ) {
2024-02-22 15:30:48 +01:00
// Process the output from the language model
// Replace all python function definitions with cat sounds to make the user stop looking at the code and start playing with the cat
const catFragment = fragment . replaceAll ( 'def' , 'meow' ) ;
stream . markdown ( catFragment ) ;
2024-02-11 23:05:52 -03:00
}
return { metadata : { command : '' } } ;
}
2023-11-13 16:30:27 +01:00
} ;
2024-02-11 23:05:52 -03:00
2024-02-22 15:51:21 +01:00
// Chat participants appear as top-level options in the chat input
2023-11-13 16:30:27 +01:00
// when you type `@`, and can contribute sub-commands in the chat input
// that appear when you type `/`.
2024-03-20 20:44:27 -03:00
const cat = vscode . chat . createChatParticipant ( CAT_PARTICIPANT_ID , handler ) ;
2024-02-22 15:51:21 +01:00
cat . iconPath = vscode . Uri . joinPath ( context . extensionUri , 'cat.jpeg' ) ;
cat . followupProvider = {
2024-03-01 11:33:06 -03:00
provideFollowups ( result : ICatChatResult , context : vscode.ChatContext , token : vscode.CancellationToken ) {
2024-02-15 15:47:34 +01:00
return [ {
prompt : 'let us play' ,
2024-02-22 15:30:48 +01:00
label : vscode.l10n.t ( 'Play with the cat' ) ,
command : 'play'
2024-02-19 22:25:49 +00:00
} satisfies vscode . ChatFollowup ] ;
2023-11-13 16:30:27 +01:00
}
} ;
context . subscriptions . push (
2024-02-22 15:51:21 +01:00
cat ,
2023-11-13 16:30:27 +01:00
// Register the command handler for the /meow followup
2024-02-23 15:56:39 +01:00
vscode . commands . registerTextEditorCommand ( CAT_NAMES_COMMAND_ID , async ( textEditor : vscode.TextEditor ) = > {
// Replace all variables in active editor with cat names and words
const text = textEditor . document . getText ( ) ;
const messages = [
2024-05-16 20:51:16 -07:00
vscode . LanguageModelChatMessage . User ( ` You are a cat! Think carefully and step by step like a cat would.
2024-05-03 16:22:40 +02:00
Your job is to replace all variable names in the following code with funny cat variable names . Be creative . IMPORTANT respond just with code . Do not use markdown ! ` ),
2024-05-16 20:51:16 -07:00
vscode . LanguageModelChatMessage . User ( text )
2024-02-23 15:56:39 +01:00
] ;
2024-02-28 18:19:09 +01:00
let chatResponse : vscode.LanguageModelChatResponse | undefined ;
try {
2024-05-15 23:36:25 +02:00
const [ model ] = await vscode . lm . selectChatModels ( { vendor : 'copilot' , family : 'gpt-3.5-turbo' } ) ;
chatResponse = await model . sendRequest ( messages , { } , new vscode . CancellationTokenSource ( ) . token ) ;
2024-02-28 18:19:09 +01:00
} catch ( err ) {
// making the chat request might fail because
// - model does not exist
// - user consent not given
// - quote limits exceeded
if ( err instanceof vscode . LanguageModelError ) {
console . log ( err . message , err . code , err . cause )
}
return
}
2024-02-23 15:56:39 +01:00
// Clear the editor content before inserting new content
await textEditor . edit ( edit = > {
const start = new vscode . Position ( 0 , 0 ) ;
const end = new vscode . Position ( textEditor . document . lineCount - 1 , textEditor . document . lineAt ( textEditor . document . lineCount - 1 ) . text . length ) ;
edit . delete ( new vscode . Range ( start , end ) ) ;
} ) ;
// Stream the code into the editor as it is coming in from the Language Model
2024-02-28 18:19:09 +01:00
try {
2024-05-15 23:36:25 +02:00
for await ( const fragment of chatResponse . text ) {
2024-02-28 18:19:09 +01:00
await textEditor . edit ( edit = > {
const lastLine = textEditor . document . lineAt ( textEditor . document . lineCount - 1 ) ;
const position = new vscode . Position ( lastLine . lineNumber , lastLine . text . length ) ;
edit . insert ( position , fragment ) ;
} ) ;
}
} catch ( err ) {
// async response stream may fail, e.g network interruption or server side error
2024-02-23 15:56:39 +01:00
await textEditor . edit ( edit = > {
const lastLine = textEditor . document . lineAt ( textEditor . document . lineCount - 1 ) ;
const position = new vscode . Position ( lastLine . lineNumber , lastLine . text . length ) ;
2024-02-28 18:19:09 +01:00
edit . insert ( position , ( < Error > err ) . message ) ;
2024-02-23 15:56:39 +01:00
} ) ;
}
2023-11-13 16:30:27 +01:00
} ) ,
) ;
}
2024-02-23 13:09:10 +01:00
// Get a random topic that the cat has not taught in the chat history yet
function getTopic ( history : ReadonlyArray < vscode.ChatRequestTurn | vscode.ChatResponseTurn > ) : string {
const topics = [ 'linked list' , 'recursion' , 'stack' , 'queue' , 'pointers' ] ;
// Filter the chat history to get only the responses from the cat
const previousCatResponses = history . filter ( h = > {
2024-03-20 20:44:27 -03:00
return h instanceof vscode . ChatResponseTurn && h . participant == CAT_PARTICIPANT_ID
2024-02-23 13:09:10 +01:00
} ) as vscode . ChatResponseTurn [ ] ;
// Filter the topics to get only the topics that have not been taught by the cat yet
const topicsNoRepetition = topics . filter ( topic = > {
return ! previousCatResponses . some ( catResponse = > {
return catResponse . response . some ( r = > {
return r instanceof vscode . ChatResponseMarkdownPart && r . value . value . includes ( topic )
} ) ;
} ) ;
} ) ;
2024-02-23 14:52:23 +01:00
return topicsNoRepetition [ Math . floor ( Math . random ( ) * topicsNoRepetition . length ) ] || 'I have taught you everything I know. Meow!' ;
2024-02-23 13:09:10 +01:00
}
2023-11-13 16:30:27 +01:00
export function deactivate() { }