diff --git a/extensions/copilot/.vscodeignore b/extensions/copilot/.vscodeignore index 98be0b8fe34..be67c2874e5 100644 --- a/extensions/copilot/.vscodeignore +++ b/extensions/copilot/.vscodeignore @@ -20,11 +20,15 @@ assets/walkthroughs/** !dist/suggestionsPanelWebview.js !node_modules/@vscode/copilot-typescript-server-plugin/package.json !node_modules/@vscode/copilot-typescript-server-plugin/dist/*.js -!node_modules/@github/copilot/**/package.json node_modules/@github/copilot/index.js +node_modules/@github/copilot/clipboard/** +node_modules/@github/copilot/prebuilds/** +node_modules/@github/copilot/sharp/** +!node_modules/@github/copilot/package.json +!node_modules/@github/copilot/sdk/**/package.json !node_modules/@github/copilot/sdk/*.js !node_modules/@github/copilot/sdk/worker/*.js -!node_modules/@github/copilot/node_modules/**/*.js +!node_modules/@github/copilot/sdk/sharp/** !CHANGELOG.md !README.md !package.json diff --git a/extensions/copilot/package-lock.json b/extensions/copilot/package-lock.json index 8e66f96f2ca..0f5b21b2bb5 100644 --- a/extensions/copilot/package-lock.json +++ b/extensions/copilot/package-lock.json @@ -13,7 +13,7 @@ "@anthropic-ai/claude-agent-sdk": "0.2.5", "@anthropic-ai/sdk": "^0.71.2", "@github/blackbird-external-ingest-utils": "^0.1.0", - "@github/copilot": "^0.0.366", + "@github/copilot": "^0.0.381", "@google/genai": "^1.22.0", "@humanwhocodes/gitignore-to-minimatch": "1.0.2", "@microsoft/tiktokenizer": "^1.0.10", @@ -3067,15 +3067,119 @@ "license": "MIT" }, "node_modules/@github/copilot": { - "version": "0.0.366", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-0.0.366.tgz", - "integrity": "sha512-mJ3wAvn4/yCrQBG5kcn2SOvWQSlrftkbpaAUBXX4l42y6Xsi4d9teuEdcwKOsUWLNDavpt3ecoCDnZUaCb/81A==", + "version": "0.0.381", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-0.0.381.tgz", + "integrity": "sha512-9AaInHlic/jQiMmm2UWitkDszmR5RihnKQBesfCBx8ow1pqZ4cx4q1DLh1l5xeJB6Cnl7Deoj5Md3FrvH8ugyw==", "license": "SEE LICENSE IN LICENSE.md", "bin": { - "copilot": "index.js" + "copilot": "npm-loader.js" }, "engines": { "node": ">=22" + }, + "optionalDependencies": { + "@github/copilot-darwin-arm64": "0.0.381", + "@github/copilot-darwin-x64": "0.0.381", + "@github/copilot-linux-arm64": "0.0.381", + "@github/copilot-linux-x64": "0.0.381", + "@github/copilot-win32-arm64": "0.0.381", + "@github/copilot-win32-x64": "0.0.381" + } + }, + "node_modules/@github/copilot-darwin-arm64": { + "version": "0.0.381", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-0.0.381.tgz", + "integrity": "sha512-Fw8bo5VkeU0NN6Ca1V+igBNYvnhi+R9lc+B8/GMEiokhVIrmxXoOFk4IUeCO33tDfmQnYpuuopyaDdaKd+YOfA==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "darwin" + ], + "bin": { + "copilot-darwin-arm64": "copilot" + } + }, + "node_modules/@github/copilot-darwin-x64": { + "version": "0.0.381", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-0.0.381.tgz", + "integrity": "sha512-z0sS/MnO/FhY9XmaVD50OssOQuSD4REatWZUyhBVnAqfVEHy0k7QKtU4Y/JvjTSoFr1Ve0S7WlKsIBZbpi7OyQ==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "darwin" + ], + "bin": { + "copilot-darwin-x64": "copilot" + } + }, + "node_modules/@github/copilot-linux-arm64": { + "version": "0.0.381", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-0.0.381.tgz", + "integrity": "sha512-GnFPEPXxT0FbbnZYJ78KzUtt8mOcqxt+DLsQ8UqrkIvQJdpQh8aJASpLGhhgSLHvtbMDc+UQjGzeigxrgqX5UQ==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ], + "bin": { + "copilot-linux-arm64": "copilot" + } + }, + "node_modules/@github/copilot-linux-x64": { + "version": "0.0.381", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-0.0.381.tgz", + "integrity": "sha512-IPNzfopRDGfGOpbyUqOyY6GpQ+IWn7NFoDJcibQxLjYGSKrt88u5/rmse0oQ1t9QYQD6GsUfQ2ILkCNXQoCjOA==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ], + "bin": { + "copilot-linux-x64": "copilot" + } + }, + "node_modules/@github/copilot-win32-arm64": { + "version": "0.0.381", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-0.0.381.tgz", + "integrity": "sha512-RsdGcDYNt6SopBM2zxKWWrn40S+Rff5UAGBG7fXyNCKjfTR6BA6jgy/x3KSKpy/1yUEqhtRZHMR0H0BMUHaZyQ==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "win32" + ], + "bin": { + "copilot-win32-arm64": "copilot.exe" + } + }, + "node_modules/@github/copilot-win32-x64": { + "version": "0.0.381", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-0.0.381.tgz", + "integrity": "sha512-IfWhMKK+h1ZZ7h4PWyDPl7+BJ0ZxAozU1Pn968rfod1Uzacdm+uOZBqK7ui0TgmQ+M5k6DCRwNHpU1e6E1s7Gw==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "win32" + ], + "bin": { + "copilot-win32-x64": "copilot.exe" } }, "node_modules/@google/genai": { diff --git a/extensions/copilot/package.json b/extensions/copilot/package.json index e01ea81f83d..bc78c1ecd0d 100644 --- a/extensions/copilot/package.json +++ b/extensions/copilot/package.json @@ -5191,7 +5191,7 @@ "@anthropic-ai/claude-agent-sdk": "0.2.5", "@anthropic-ai/sdk": "^0.71.2", "@github/blackbird-external-ingest-utils": "^0.1.0", - "@github/copilot": "^0.0.366", + "@github/copilot": "^0.0.381", "@google/genai": "^1.22.0", "@humanwhocodes/gitignore-to-minimatch": "1.0.2", "@microsoft/tiktokenizer": "^1.0.10", diff --git a/extensions/copilot/script/postinstall.ts b/extensions/copilot/script/postinstall.ts index 02a3f8fe83a..3f7c0cd3802 100644 --- a/extensions/copilot/script/postinstall.ts +++ b/extensions/copilot/script/postinstall.ts @@ -61,60 +61,6 @@ const treeSitterGrammars: ITreeSitterGrammar[] = [ const REPO_ROOT = path.join(__dirname, '..'); -/** - * @github/copilot depends on sharp which has native dependencies that are hard to distribute. - * This function creates a shim for the sharp module that @github/copilot expects. - * The shim provides a minimal implementation of the sharp API to satisfy @github/copilot's requirements. - * Its non-functional and only intended to make the module load without errors. - * - * We create a directory @github/copilot/node_modules/sharp, so that - * the node module resolution algorithm finds our shim instead of trying to load the real sharp module. This also ensure the shims are specific to this package. - */ -async function createCopilotCliSharpShim() { - const copilotCli = path.join(REPO_ROOT, 'node_modules', '@github', 'copilot'); - const sharpShim = path.join(copilotCli, 'node_modules', 'sharp'); - - const copilotPackageJsonFile = path.join(copilotCli, 'package.json'); - const copilotPackageJson = JSON.parse(fs.readFileSync(copilotPackageJsonFile, 'utf-8')); - if (copilotPackageJson.dependencies) { - delete copilotPackageJson.dependencies.sharp; - } - - await fs.promises.writeFile(copilotPackageJsonFile, JSON.stringify(copilotPackageJson, undefined, 2), 'utf-8'); - await fs.promises.rm(sharpShim, { recursive: true, force: true }); - await fs.promises.mkdir(path.join(sharpShim, 'lib'), { recursive: true }); - await fs.promises.writeFile(path.join(sharpShim, 'package.json'), JSON.stringify({ - "name": "sharp", - "type": "commonjs", - "main": "lib/index.js" - }, undefined, 2)); - await fs.promises.writeFile(path.join(sharpShim, 'lib', 'index.js'), ` -const Sharp = function (inputBuffer, options) { - if (arguments.length === 1 && !is.defined(input)) { - throw new Error('Invalid input'); - } - if (!(this instanceof Sharp)) { - return new Sharp(input, options); - } - this.inputBuffer = inputBuffer; - return this; -}; - -Sharp.prototype.resize = function () { - const that = this; - const img = { - toBuffer: () => that.inputBuffer, - png: () => img, - jpeg: () => img - }; - return img; -}; - -module.exports = Sharp; -`); - -} - /** * @github/copilot/sdk/index.js depends on @github/copilot/worker/*.js files. * We need to copy these files into the sdk directory to ensure they are available at runtime. @@ -123,6 +69,17 @@ async function copyCopilotCliWorkerFiles() { const sourceDir = path.join(REPO_ROOT, 'node_modules', '@github', 'copilot', 'worker'); const targetDir = path.join(REPO_ROOT, 'node_modules', '@github', 'copilot', 'sdk', 'worker'); + await copyCopilotCLIFolders(sourceDir, targetDir); +} + +async function copyCopilotCliSharpFiles() { + const sourceDir = path.join(REPO_ROOT, 'node_modules', '@github', 'copilot', 'sharp'); + const targetDir = path.join(REPO_ROOT, 'node_modules', '@github', 'copilot', 'sdk', 'sharp'); + + await copyCopilotCLIFolders(sourceDir, targetDir); +} + +async function copyCopilotCLIFolders(sourceDir: string, targetDir: string) { await fs.promises.rm(targetDir, { recursive: true, force: true }); await fs.promises.mkdir(targetDir, { recursive: true }); await fs.promises.cp(sourceDir, targetDir, { recursive: true, force: true }); @@ -144,8 +101,8 @@ async function main() { 'node_modules/@github/blackbird-external-ingest-utils/pkg/nodejs/external_ingest_utils_bg.wasm', ], 'dist'); - await createCopilotCliSharpShim(); await copyCopilotCliWorkerFiles(); + await copyCopilotCliSharpFiles(); // Check if the base cache file exists const baseCachePath = path.join('test', 'simulation', 'cache', 'base.sqlite'); diff --git a/extensions/copilot/src/extension/agents/copilotcli/common/copilotCLITools.ts b/extensions/copilot/src/extension/agents/copilotcli/common/copilotCLITools.ts index 25b68f3dfa9..61f44e2bd60 100644 --- a/extensions/copilot/src/extension/agents/copilotcli/common/copilotCLITools.ts +++ b/extensions/copilot/src/extension/agents/copilotcli/common/copilotCLITools.ts @@ -403,11 +403,15 @@ export function buildChatHistoryFromEvents(sessionId: string, events: readonly S turns.push(new ChatRequestTurn2(prompt, undefined, references, '', [], undefined, details?.requestId)); break; } - case 'assistant.message': { - if (typeof event.data.chunkContent === 'string') { + case 'assistant.message_delta': { + if (typeof event.data.deltaContent === 'string') { processedMessages.add(event.data.messageId); - currentAssistantMessage.chunks.push(event.data.chunkContent); - } else if (event.data.content && !processedMessages.has(event.data.messageId)) { + currentAssistantMessage.chunks.push(event.data.deltaContent); + } + break; + } + case 'assistant.message': { + if (event.data.content && !processedMessages.has(event.data.messageId)) { processAssistantMessage(event.data.content); } break; @@ -566,6 +570,7 @@ function formatProgressToolInvocation(invocation: ChatToolInvocationPart, toolCa } + function formatViewToolInvocation(invocation: ChatToolInvocationPart, toolCall: ViewTool): void { const args = toolCall.arguments; diff --git a/extensions/copilot/src/extension/agents/copilotcli/node/copilotCli.ts b/extensions/copilot/src/extension/agents/copilotcli/node/copilotCli.ts index 09239d4efa4..bf2cc1037b8 100644 --- a/extensions/copilot/src/extension/agents/copilotcli/node/copilotCli.ts +++ b/extensions/copilot/src/extension/agents/copilotcli/node/copilotCli.ts @@ -39,7 +39,6 @@ export class CopilotCLISessionOptions { private readonly agent?: SweCustomAgent; private readonly customAgents?: SweCustomAgent[]; private readonly mcpServers?: SessionOptions['mcpServers']; - private readonly logger: ReturnType; private readonly requestPermissionRejected: NonNullable; private requestPermissionHandler: NonNullable; constructor(options: { model?: string; isolationEnabled?: boolean; workingDirectory?: Uri; mcpServers?: SessionOptions['mcpServers']; agent?: SweCustomAgent; customAgents?: SweCustomAgent[] }, logger: ILogService) { @@ -49,7 +48,6 @@ export class CopilotCLISessionOptions { this.mcpServers = options.mcpServers; this.agent = options.agent; this.customAgents = options.customAgents; - this.logger = getCopilotLogger(logger); this.requestPermissionRejected = async (permission: PermissionRequest): ReturnType> => { logger.info(`[CopilotCLISession] Permission request denied for permission as no handler was set: ${permission.kind}`); return { @@ -70,7 +68,6 @@ export class CopilotCLISessionOptions { public toSessionOptions(): Readonly }> { const allOptions: SessionOptions = { - logger: this.logger, requestPermission: async (request: PermissionRequest) => { return await this.requestPermissionHandler(request); } @@ -148,7 +145,7 @@ export class CopilotCLIModels implements ICopilotCLIModels { const [{ getAvailableModels }, authInfo] = await Promise.all([this.copilotCLISDK.getPackage(), this.copilotCLISDK.getAuthInfo()]); try { const models = await getAvailableModels(authInfo); - return models.map(model => ({ id: model.model, name: model.label })); + return models.map(model => ({ id: model.id, name: model.name })); } catch (ex) { this.logService.error(`[CopilotCLISession] Failed to fetch models`, ex); return []; diff --git a/extensions/copilot/src/extension/agents/copilotcli/node/copilotcliSession.ts b/extensions/copilot/src/extension/agents/copilotcli/node/copilotcliSession.ts index 3b266c243c7..b2a6a99414a 100644 --- a/extensions/copilot/src/extension/agents/copilotcli/node/copilotcliSession.ts +++ b/extensions/copilot/src/extension/agents/copilotcli/node/copilotcliSession.ts @@ -189,17 +189,21 @@ export class CopilotCLISession extends DisposableStore implements ICopilotCLISes await raceCancellation(this._sdkSession.setSelectedModel(modelId), token); } - disposables.add(toDisposable(this._sdkSession.on('*', (event) => this.logService.trace(`[CopilotCLISession]CopilotCLI Event: ${JSON.stringify(event, null, 2)}`)))); + disposables.add(toDisposable(this._sdkSession.on('*', (event) => { + this.logService.trace(`[CopilotCLISession] CopilotCLI Event: ${JSON.stringify(event, null, 2)}`); + }))); disposables.add(toDisposable(this._sdkSession.on('user.message', (event) => { sdkRequestId = event.id; }))); - disposables.add(toDisposable(this._sdkSession.on('assistant.message', (event) => { - // Support for streaming chunked messages. - if (typeof event.data.chunkContent === 'string' && event.data.chunkContent.length) { + disposables.add(toDisposable(this._sdkSession.on('assistant.message_delta', (event) => { + // Support for streaming delta messages. + if (typeof event.data.deltaContent === 'string' && event.data.deltaContent.length) { chunkMessageIds.add(event.data.messageId); - assistantMessageChunks.push(event.data.chunkContent); - this._stream?.markdown(event.data.chunkContent); + assistantMessageChunks.push(event.data.deltaContent); + this._stream?.markdown(event.data.deltaContent); } + }))); + disposables.add(toDisposable(this._sdkSession.on('assistant.message', (event) => { if (typeof event.data.content === 'string' && event.data.content.length && !chunkMessageIds.has(event.data.messageId)) { assistantMessageChunks.push(event.data.content); this._stream?.markdown(event.data.content); diff --git a/extensions/copilot/src/extension/agents/copilotcli/node/copilotcliSessionService.ts b/extensions/copilot/src/extension/agents/copilotcli/node/copilotcliSessionService.ts index a2564a5986f..57761d30f49 100644 --- a/extensions/copilot/src/extension/agents/copilotcli/node/copilotcliSessionService.ts +++ b/extensions/copilot/src/extension/agents/copilotcli/node/copilotcliSessionService.ts @@ -24,7 +24,6 @@ import { ChatSessionStatus } from '../../../../vscodeTypes'; import { stripReminders } from '../common/copilotCLITools'; import { CopilotCLISessionOptions, ICopilotCLIAgents, ICopilotCLISDK } from './copilotCli'; import { CopilotCLISession, ICopilotCLISession } from './copilotcliSession'; -import { getCopilotLogger } from './logger'; import { ICopilotCLIMCPHandler } from './mcpHandler'; const COPILOT_CLI_WORKSPACE_JSON_FILE_KEY = 'github.copilot.cli.workspaceSessionFile'; @@ -86,9 +85,7 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS this.monitorSessionFiles(); this._sessionManager = new Lazy>(async () => { const { internal } = await this.copilotCLISDK.getPackage(); - return new internal.LocalSessionManager({ - logger: getCopilotLogger(this.logService) - }); + return new internal.LocalSessionManager({}); }); this._sessionTracker = this.instantiationService.createInstance(CopilotCLISessionWorkspaceTracker); } diff --git a/extensions/copilot/src/extension/agents/copilotcli/node/test/permissionHelpers.spec.ts b/extensions/copilot/src/extension/agents/copilotcli/node/test/permissionHelpers.spec.ts index e0446d9be0b..7f92ca48aa4 100644 --- a/extensions/copilot/src/extension/agents/copilotcli/node/test/permissionHelpers.spec.ts +++ b/extensions/copilot/src/extension/agents/copilotcli/node/test/permissionHelpers.spec.ts @@ -155,7 +155,7 @@ describe('CopilotCLI permissionHelpers', () => { describe('getConfirmationToolParams', () => { it('maps shell requests to terminal confirmation tool', async () => { - const result = await getConfirmationToolParams(instaService, { kind: 'shell', fullCommandText: 'rm -rf /tmp/test', canOfferSessionApproval: true, commands: [], hasWriteFileRedirection: true, intention: '', possiblePaths: [] }); + const result = await getConfirmationToolParams(instaService, { kind: 'shell', fullCommandText: 'rm -rf /tmp/test', canOfferSessionApproval: true, commands: [], hasWriteFileRedirection: true, intention: '', possiblePaths: [], possibleUrls: [] }); assert(!!result); expect(result.tool).toBe(ToolName.CoreTerminalConfirmationTool); }); diff --git a/extensions/copilot/src/extension/chatSessions/vscode-node/test/copilotCLISDKUpgrade.spec.ts b/extensions/copilot/src/extension/chatSessions/vscode-node/test/copilotCLISDKUpgrade.spec.ts index 3015eb5d360..48955bf9d91 100644 --- a/extensions/copilot/src/extension/chatSessions/vscode-node/test/copilotCLISDKUpgrade.spec.ts +++ b/extensions/copilot/src/extension/chatSessions/vscode-node/test/copilotCLISDKUpgrade.spec.ts @@ -55,20 +55,9 @@ describe('CopilotCLI SDK Upgrade', function () { path.join('ripgrep', 'bin', 'linux-x64', 'rg'), path.join('ripgrep', 'bin', 'linux-arm64', 'rg'), // sharp related files - path.join('sharp', 'node_modules', '@img', 'sharp-libvips-linux-arm64', 'lib', 'libvips-cpp.so.8.17.3'), - path.join('sharp', 'node_modules', '@img', 'sharp-libvips-linux-x64', 'lib', 'libvips-cpp.so.8.17.3'), - path.join('sharp', 'node_modules', '@img', 'sharp-darwin-arm64', 'lib', 'sharp-darwin-arm64.node'), - path.join('sharp', 'node_modules', '@img', 'sharp-darwin-x64', 'lib', 'sharp-darwin-x64.node'), - path.join('sharp', 'node_modules', '@img', 'sharp-libvips-darwin-arm64', 'lib', 'libvips-cpp.8.17.3.dylib'), - path.join('sharp', 'node_modules', '@img', 'sharp-libvips-darwin-x64', 'lib', 'libvips-cpp.8.17.3.dylib'), - path.join('sharp', 'node_modules', '@img', 'sharp-linux-arm64', 'lib', 'sharp-linux-arm64.node'), - path.join('sharp', 'node_modules', '@img', 'sharp-linux-x64', 'lib', 'sharp-linux-x64.node'), - path.join('sharp', 'node_modules', '@img', 'sharp-win32-arm64', 'lib', 'libvips-42.dll'), - path.join('sharp', 'node_modules', '@img', 'sharp-win32-arm64', 'lib', 'libvips-cpp-8.17.3.dll'), - path.join('sharp', 'node_modules', '@img', 'sharp-win32-arm64', 'lib', 'sharp-win32-arm64.node'), - path.join('sharp', 'node_modules', '@img', 'sharp-win32-x64', 'lib', 'libvips-42.dll'), - path.join('sharp', 'node_modules', '@img', 'sharp-win32-x64', 'lib', 'libvips-cpp-8.17.3.dll'), - path.join('sharp', 'node_modules', '@img', 'sharp-win32-x64', 'lib', 'sharp-win32-x64.node'), + path.join('sharp', 'node_modules', '@img', 'sharp-wasm32', 'lib', 'sharp-wasm32.node.wasm'), + // sharp related files, files copied by us. + path.join('sdk', 'sharp', 'node_modules', '@img', 'sharp-wasm32', 'lib', 'sharp-wasm32.node.wasm'), // parsing commands for shell. 'tree-sitter-bash.wasm', 'tree-sitter-powershell.wasm', @@ -134,4 +123,4 @@ async function findAllBinaries(dir: string): Promise { await findFilesRecursively(dir); return binaryFiles; -} \ No newline at end of file +} diff --git a/extensions/copilot/test/e2e/cli.stest.ts b/extensions/copilot/test/e2e/cli.stest.ts index 4eb6b8c4f69..45a797c77a1 100644 --- a/extensions/copilot/test/e2e/cli.stest.ts +++ b/extensions/copilot/test/e2e/cli.stest.ts @@ -6,6 +6,7 @@ import { SessionOptions } from '@github/copilot/sdk'; import assert from 'assert'; import * as fs from 'fs/promises'; +import * as http from 'http'; import { platform, tmpdir } from 'os'; import * as path from 'path'; import type { ChatPromptReference } from 'vscode'; @@ -117,7 +118,6 @@ function registerChatServices(testingServiceCollection: TestingServiceCollection mutableOptions.copilotUrl = this.testOptions.copilotUrl ?? options.copilotUrl; mutableOptions.enableStreaming = true; mutableOptions.skipCustomInstructions = true; - mutableOptions.disableHttpLogging = true; return options; } } @@ -145,6 +145,41 @@ function registerChatServices(testingServiceCollection: TestingServiceCollection this.adapterFactories.set('/chat/completions', oaiAdapterFactory); requestHooks.forEach(requestHook => oaiAdapterFactory.addHooks(requestHook)); responseHooks.forEach(responseHook => oaiAdapterFactory.addHooks(undefined, responseHook)); + this.requestHandlers.set('/graphql', { method: 'POST', handler: this.graphqlHandler.bind(this) }); + this.requestHandlers.set('/models', { method: 'GET', handler: this.modelsHandler.bind(this) }); + } + + private async graphqlHandler(req: http.IncomingMessage, res: http.ServerResponse): Promise { + res.writeHead(200, { 'Content-Type': 'application/json' }); + const data = { + viewer: { + login: '', + copilotEndpoints: { + api: `http://localhost:${this.config.port}` + } + } + }; + res.end(JSON.stringify({ data })); + } + private async modelsHandler(req: http.IncomingMessage, res: http.ServerResponse): Promise { + res.writeHead(200, { 'Content-Type': 'application/json', 'x-github-request-id': 'TESTREQUESTID1234' }); + const endpoints = await this.endpointProvider.getAllChatEndpoints(); + const data = endpoints.map(e => { + return { + id: e.model, + name: e.model, + capabilities: { + supports: { + vision: e.supportsVision, + }, + limits: { + max_prompt_tokens: e.modelMaxPromptTokens, + max_context_window_tokens: e.maxOutputTokens, + } + } + }; + }); + res.end(JSON.stringify({ data })); } }