@github/copilot-sdk
Advanced tools
| import { CopilotSession } from "./session.js"; | ||
| import type { ConnectionState, CopilotClientOptions, ResumeSessionConfig, SessionConfig, SessionMetadata } from "./types.js"; | ||
| export declare class CopilotClient { | ||
| private cliProcess; | ||
| private connection; | ||
| private socket; | ||
| private actualPort; | ||
| private actualHost; | ||
| private state; | ||
| private sessions; | ||
| private options; | ||
| private isExternalServer; | ||
| private forceStopping; | ||
| constructor(options?: CopilotClientOptions); | ||
| /** | ||
| * Parse CLI URL into host and port | ||
| * Supports formats: "host:port", "http://host:port", "https://host:port", or just "port" | ||
| */ | ||
| private parseCliUrl; | ||
| /** | ||
| * Start the CLI server and establish connection | ||
| */ | ||
| start(): Promise<void>; | ||
| /** | ||
| * Stop the CLI server and close all sessions | ||
| * Returns array of errors encountered during cleanup (empty if all succeeded) | ||
| */ | ||
| stop(): Promise<Error[]>; | ||
| /** | ||
| * Force stop the CLI server without graceful cleanup | ||
| * Use when normal stop() fails or takes too long | ||
| */ | ||
| forceStop(): Promise<void>; | ||
| /** | ||
| * Create a new session | ||
| */ | ||
| createSession(config?: SessionConfig): Promise<CopilotSession>; | ||
| /** | ||
| * Resume an existing session | ||
| */ | ||
| resumeSession(sessionId: string, config?: ResumeSessionConfig): Promise<CopilotSession>; | ||
| /** | ||
| * Get connection state | ||
| */ | ||
| getState(): ConnectionState; | ||
| /** | ||
| * Ping the server | ||
| */ | ||
| ping(message?: string): Promise<{ | ||
| message: string; | ||
| timestamp: number; | ||
| }>; | ||
| /** | ||
| * Get the ID of the most recently updated session | ||
| * @returns The session ID, or undefined if no sessions exist | ||
| */ | ||
| getLastSessionId(): Promise<string | undefined>; | ||
| /** | ||
| * Delete a session and its data from disk | ||
| * @param sessionId The ID of the session to delete | ||
| */ | ||
| deleteSession(sessionId: string): Promise<void>; | ||
| /** | ||
| * List all available sessions | ||
| * @returns Array of session metadata | ||
| */ | ||
| listSessions(): Promise<SessionMetadata[]>; | ||
| /** | ||
| * Start the CLI server process | ||
| */ | ||
| private startCLIServer; | ||
| /** | ||
| * Connect to the CLI server (via socket or stdio) | ||
| */ | ||
| private connectToServer; | ||
| /** | ||
| * Connect via stdio pipes | ||
| */ | ||
| private connectViaStdio; | ||
| /** | ||
| * Connect to the CLI server via TCP socket | ||
| */ | ||
| private connectViaTcp; | ||
| private attachConnectionHandlers; | ||
| private handleSessionEventNotification; | ||
| private handleToolCallRequest; | ||
| private executeToolCall; | ||
| private normalizeToolResult; | ||
| private isToolResultObject; | ||
| private buildUnsupportedToolResult; | ||
| /** | ||
| * Attempt to reconnect to the server | ||
| */ | ||
| private reconnect; | ||
| } |
+549
| import { spawn } from "node:child_process"; | ||
| import { Socket } from "node:net"; | ||
| import { | ||
| createMessageConnection, | ||
| StreamMessageReader, | ||
| StreamMessageWriter | ||
| } from "vscode-jsonrpc/node.js"; | ||
| import { CopilotSession } from "./session.js"; | ||
| function isZodSchema(value) { | ||
| return value != null && typeof value === "object" && "toJSONSchema" in value && typeof value.toJSONSchema === "function"; | ||
| } | ||
| function toJsonSchema(parameters) { | ||
| if (!parameters) return void 0; | ||
| if (isZodSchema(parameters)) { | ||
| return parameters.toJSONSchema(); | ||
| } | ||
| return parameters; | ||
| } | ||
| class CopilotClient { | ||
| cliProcess = null; | ||
| connection = null; | ||
| socket = null; | ||
| actualPort = null; | ||
| actualHost = "localhost"; | ||
| state = "disconnected"; | ||
| sessions = /* @__PURE__ */ new Map(); | ||
| options; | ||
| isExternalServer = false; | ||
| forceStopping = false; | ||
| constructor(options = {}) { | ||
| if (options.cliUrl && (options.useStdio === true || options.cliPath)) { | ||
| throw new Error("cliUrl is mutually exclusive with useStdio and cliPath"); | ||
| } | ||
| if (options.cliUrl) { | ||
| const { host, port } = this.parseCliUrl(options.cliUrl); | ||
| this.actualHost = host; | ||
| this.actualPort = port; | ||
| this.isExternalServer = true; | ||
| } | ||
| this.options = { | ||
| cliPath: options.cliPath || "copilot", | ||
| cliArgs: options.cliArgs ?? [], | ||
| cwd: options.cwd ?? process.cwd(), | ||
| port: options.port || 0, | ||
| useStdio: options.cliUrl ? false : options.useStdio ?? true, | ||
| // Default to stdio unless cliUrl is provided | ||
| cliUrl: options.cliUrl, | ||
| logLevel: options.logLevel || "info", | ||
| autoStart: options.autoStart ?? true, | ||
| autoRestart: options.autoRestart ?? true, | ||
| env: options.env ?? process.env | ||
| }; | ||
| } | ||
| /** | ||
| * Parse CLI URL into host and port | ||
| * Supports formats: "host:port", "http://host:port", "https://host:port", or just "port" | ||
| */ | ||
| parseCliUrl(url) { | ||
| let cleanUrl = url.replace(/^https?:\/\//, ""); | ||
| if (/^\d+$/.test(cleanUrl)) { | ||
| return { host: "localhost", port: parseInt(cleanUrl, 10) }; | ||
| } | ||
| const parts = cleanUrl.split(":"); | ||
| if (parts.length !== 2) { | ||
| throw new Error( | ||
| `Invalid cliUrl format: ${url}. Expected "host:port", "http://host:port", or "port"` | ||
| ); | ||
| } | ||
| const host = parts[0] || "localhost"; | ||
| const port = parseInt(parts[1], 10); | ||
| if (isNaN(port) || port <= 0 || port > 65535) { | ||
| throw new Error(`Invalid port in cliUrl: ${url}`); | ||
| } | ||
| return { host, port }; | ||
| } | ||
| /** | ||
| * Start the CLI server and establish connection | ||
| */ | ||
| async start() { | ||
| if (this.state === "connected") { | ||
| return; | ||
| } | ||
| this.state = "connecting"; | ||
| try { | ||
| if (!this.isExternalServer) { | ||
| await this.startCLIServer(); | ||
| } | ||
| await this.connectToServer(); | ||
| this.state = "connected"; | ||
| } catch (error) { | ||
| this.state = "error"; | ||
| throw error; | ||
| } | ||
| } | ||
| /** | ||
| * Stop the CLI server and close all sessions | ||
| * Returns array of errors encountered during cleanup (empty if all succeeded) | ||
| */ | ||
| async stop() { | ||
| const errors = []; | ||
| for (const session of this.sessions.values()) { | ||
| const sessionId = session.sessionId; | ||
| let lastError = null; | ||
| for (let attempt = 1; attempt <= 3; attempt++) { | ||
| try { | ||
| await session.destroy(); | ||
| lastError = null; | ||
| break; | ||
| } catch (error) { | ||
| lastError = error instanceof Error ? error : new Error(String(error)); | ||
| if (attempt < 3) { | ||
| const delay = 100 * Math.pow(2, attempt - 1); | ||
| await new Promise((resolve) => setTimeout(resolve, delay)); | ||
| } | ||
| } | ||
| } | ||
| if (lastError) { | ||
| errors.push( | ||
| new Error( | ||
| `Failed to destroy session ${sessionId} after 3 attempts: ${lastError.message}` | ||
| ) | ||
| ); | ||
| } | ||
| } | ||
| this.sessions.clear(); | ||
| if (this.connection) { | ||
| try { | ||
| this.connection.dispose(); | ||
| } catch (error) { | ||
| errors.push( | ||
| new Error( | ||
| `Failed to dispose connection: ${error instanceof Error ? error.message : String(error)}` | ||
| ) | ||
| ); | ||
| } | ||
| this.connection = null; | ||
| } | ||
| if (this.socket) { | ||
| try { | ||
| this.socket.end(); | ||
| } catch (error) { | ||
| errors.push( | ||
| new Error( | ||
| `Failed to close socket: ${error instanceof Error ? error.message : String(error)}` | ||
| ) | ||
| ); | ||
| } | ||
| this.socket = null; | ||
| } | ||
| if (this.cliProcess && !this.isExternalServer) { | ||
| try { | ||
| this.cliProcess.kill(); | ||
| } catch (error) { | ||
| errors.push( | ||
| new Error( | ||
| `Failed to kill CLI process: ${error instanceof Error ? error.message : String(error)}` | ||
| ) | ||
| ); | ||
| } | ||
| this.cliProcess = null; | ||
| } | ||
| this.state = "disconnected"; | ||
| this.actualPort = null; | ||
| return errors; | ||
| } | ||
| /** | ||
| * Force stop the CLI server without graceful cleanup | ||
| * Use when normal stop() fails or takes too long | ||
| */ | ||
| async forceStop() { | ||
| this.forceStopping = true; | ||
| this.sessions.clear(); | ||
| if (this.connection) { | ||
| try { | ||
| this.connection.dispose(); | ||
| } catch { | ||
| } | ||
| this.connection = null; | ||
| } | ||
| if (this.socket) { | ||
| try { | ||
| this.socket.destroy(); | ||
| } catch { | ||
| } | ||
| this.socket = null; | ||
| } | ||
| if (this.cliProcess && !this.isExternalServer) { | ||
| try { | ||
| this.cliProcess.kill("SIGKILL"); | ||
| } catch { | ||
| } | ||
| this.cliProcess = null; | ||
| } | ||
| this.state = "disconnected"; | ||
| this.actualPort = null; | ||
| } | ||
| /** | ||
| * Create a new session | ||
| */ | ||
| async createSession(config = {}) { | ||
| if (!this.connection) { | ||
| if (this.options.autoStart) { | ||
| await this.start(); | ||
| } else { | ||
| throw new Error("Client not connected. Call start() first."); | ||
| } | ||
| } | ||
| const response = await this.connection.sendRequest("session.create", { | ||
| model: config.model, | ||
| sessionId: config.sessionId, | ||
| tools: config.tools?.map((tool) => ({ | ||
| name: tool.name, | ||
| description: tool.description, | ||
| parameters: toJsonSchema(tool.parameters) | ||
| })), | ||
| systemMessage: config.systemMessage, | ||
| availableTools: config.availableTools, | ||
| excludedTools: config.excludedTools, | ||
| provider: config.provider | ||
| }); | ||
| const sessionId = response.sessionId; | ||
| const session = new CopilotSession(sessionId, this.connection); | ||
| session.registerTools(config.tools); | ||
| this.sessions.set(sessionId, session); | ||
| return session; | ||
| } | ||
| /** | ||
| * Resume an existing session | ||
| */ | ||
| async resumeSession(sessionId, config = {}) { | ||
| if (!this.connection) { | ||
| if (this.options.autoStart) { | ||
| await this.start(); | ||
| } else { | ||
| throw new Error("Client not connected. Call start() first."); | ||
| } | ||
| } | ||
| const response = await this.connection.sendRequest("session.resume", { | ||
| sessionId, | ||
| tools: config.tools?.map((tool) => ({ | ||
| name: tool.name, | ||
| description: tool.description, | ||
| parameters: toJsonSchema(tool.parameters) | ||
| })), | ||
| provider: config.provider | ||
| }); | ||
| const resumedSessionId = response.sessionId; | ||
| const session = new CopilotSession(resumedSessionId, this.connection); | ||
| session.registerTools(config.tools); | ||
| this.sessions.set(resumedSessionId, session); | ||
| return session; | ||
| } | ||
| /** | ||
| * Get connection state | ||
| */ | ||
| getState() { | ||
| return this.state; | ||
| } | ||
| /** | ||
| * Ping the server | ||
| */ | ||
| async ping(message) { | ||
| if (!this.connection) { | ||
| throw new Error("Client not connected"); | ||
| } | ||
| const result = await this.connection.sendRequest("ping", { message }); | ||
| return result; | ||
| } | ||
| /** | ||
| * Get the ID of the most recently updated session | ||
| * @returns The session ID, or undefined if no sessions exist | ||
| */ | ||
| async getLastSessionId() { | ||
| if (!this.connection) { | ||
| throw new Error("Client not connected"); | ||
| } | ||
| const response = await this.connection.sendRequest("session.getLastId", {}); | ||
| return response.sessionId; | ||
| } | ||
| /** | ||
| * Delete a session and its data from disk | ||
| * @param sessionId The ID of the session to delete | ||
| */ | ||
| async deleteSession(sessionId) { | ||
| if (!this.connection) { | ||
| throw new Error("Client not connected"); | ||
| } | ||
| const response = await this.connection.sendRequest("session.delete", { | ||
| sessionId | ||
| }); | ||
| const { success, error } = response; | ||
| if (!success) { | ||
| throw new Error(`Failed to delete session ${sessionId}: ${error || "Unknown error"}`); | ||
| } | ||
| this.sessions.delete(sessionId); | ||
| } | ||
| /** | ||
| * List all available sessions | ||
| * @returns Array of session metadata | ||
| */ | ||
| async listSessions() { | ||
| if (!this.connection) { | ||
| throw new Error("Client not connected"); | ||
| } | ||
| const response = await this.connection.sendRequest("session.list", {}); | ||
| const { sessions } = response; | ||
| return sessions.map((s) => ({ | ||
| sessionId: s.sessionId, | ||
| startTime: new Date(s.startTime), | ||
| modifiedTime: new Date(s.modifiedTime), | ||
| summary: s.summary, | ||
| isRemote: s.isRemote | ||
| })); | ||
| } | ||
| /** | ||
| * Start the CLI server process | ||
| */ | ||
| async startCLIServer() { | ||
| return new Promise((resolve, reject) => { | ||
| const args = [ | ||
| ...this.options.cliArgs, | ||
| "--server", | ||
| "--log-level", | ||
| this.options.logLevel | ||
| ]; | ||
| if (this.options.useStdio) { | ||
| args.push("--stdio"); | ||
| } else if (this.options.port > 0) { | ||
| args.push("--port", this.options.port.toString()); | ||
| } | ||
| const envWithoutNodeDebug = { ...this.options.env }; | ||
| delete envWithoutNodeDebug.NODE_DEBUG; | ||
| const isJsFile = this.options.cliPath.endsWith(".js"); | ||
| const command = isJsFile ? "node" : this.options.cliPath; | ||
| const spawnArgs = isJsFile ? [this.options.cliPath, ...args] : args; | ||
| this.cliProcess = spawn(command, spawnArgs, { | ||
| stdio: this.options.useStdio ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"], | ||
| cwd: this.options.cwd, | ||
| env: envWithoutNodeDebug | ||
| }); | ||
| let stdout = ""; | ||
| let resolved = false; | ||
| if (this.options.useStdio) { | ||
| resolved = true; | ||
| resolve(); | ||
| } else { | ||
| this.cliProcess.stdout?.on("data", (data) => { | ||
| stdout += data.toString(); | ||
| const match = stdout.match(/listening on port (\d+)/i); | ||
| if (match && !resolved) { | ||
| this.actualPort = parseInt(match[1], 10); | ||
| resolved = true; | ||
| resolve(); | ||
| } | ||
| }); | ||
| } | ||
| this.cliProcess.stderr?.on("data", (data) => { | ||
| const lines = data.toString().split("\n"); | ||
| for (const line of lines) { | ||
| if (line.trim()) { | ||
| process.stderr.write(`[CLI subprocess] ${line} | ||
| `); | ||
| } | ||
| } | ||
| }); | ||
| this.cliProcess.on("error", (error) => { | ||
| if (!resolved) { | ||
| resolved = true; | ||
| reject(new Error(`Failed to start CLI server: ${error.message}`)); | ||
| } | ||
| }); | ||
| this.cliProcess.on("exit", (code) => { | ||
| if (!resolved) { | ||
| resolved = true; | ||
| reject(new Error(`CLI server exited with code ${code}`)); | ||
| } else if (this.options.autoRestart && this.state === "connected") { | ||
| void this.reconnect(); | ||
| } | ||
| }); | ||
| setTimeout(() => { | ||
| if (!resolved) { | ||
| resolved = true; | ||
| reject(new Error("Timeout waiting for CLI server to start")); | ||
| } | ||
| }, 1e4); | ||
| }); | ||
| } | ||
| /** | ||
| * Connect to the CLI server (via socket or stdio) | ||
| */ | ||
| async connectToServer() { | ||
| if (this.options.useStdio) { | ||
| return this.connectViaStdio(); | ||
| } else { | ||
| return this.connectViaTcp(); | ||
| } | ||
| } | ||
| /** | ||
| * Connect via stdio pipes | ||
| */ | ||
| async connectViaStdio() { | ||
| if (!this.cliProcess) { | ||
| throw new Error("CLI process not started"); | ||
| } | ||
| this.cliProcess.stdin?.on("error", (err) => { | ||
| if (!this.forceStopping) { | ||
| throw err; | ||
| } | ||
| }); | ||
| this.connection = createMessageConnection( | ||
| new StreamMessageReader(this.cliProcess.stdout), | ||
| new StreamMessageWriter(this.cliProcess.stdin) | ||
| ); | ||
| this.attachConnectionHandlers(); | ||
| this.connection.listen(); | ||
| } | ||
| /** | ||
| * Connect to the CLI server via TCP socket | ||
| */ | ||
| async connectViaTcp() { | ||
| if (!this.actualPort) { | ||
| throw new Error("Server port not available"); | ||
| } | ||
| return new Promise((resolve, reject) => { | ||
| this.socket = new Socket(); | ||
| this.socket.connect(this.actualPort, this.actualHost, () => { | ||
| this.connection = createMessageConnection( | ||
| new StreamMessageReader(this.socket), | ||
| new StreamMessageWriter(this.socket) | ||
| ); | ||
| this.attachConnectionHandlers(); | ||
| this.connection.listen(); | ||
| resolve(); | ||
| }); | ||
| this.socket.on("error", (error) => { | ||
| reject(new Error(`Failed to connect to CLI server: ${error.message}`)); | ||
| }); | ||
| }); | ||
| } | ||
| attachConnectionHandlers() { | ||
| if (!this.connection) { | ||
| return; | ||
| } | ||
| this.connection.onNotification("session.event", (notification) => { | ||
| this.handleSessionEventNotification(notification); | ||
| }); | ||
| this.connection.onRequest( | ||
| "tool.call", | ||
| async (params) => await this.handleToolCallRequest(params) | ||
| ); | ||
| this.connection.onClose(() => { | ||
| if (this.state === "connected" && this.options.autoRestart) { | ||
| void this.reconnect(); | ||
| } | ||
| }); | ||
| this.connection.onError((_error) => { | ||
| }); | ||
| } | ||
| handleSessionEventNotification(notification) { | ||
| if (typeof notification !== "object" || !notification || !("sessionId" in notification) || typeof notification.sessionId !== "string" || !("event" in notification)) { | ||
| return; | ||
| } | ||
| const session = this.sessions.get(notification.sessionId); | ||
| if (session) { | ||
| session._dispatchEvent(notification.event); | ||
| } | ||
| } | ||
| async handleToolCallRequest(params) { | ||
| if (!params || typeof params.sessionId !== "string" || typeof params.toolCallId !== "string" || typeof params.toolName !== "string") { | ||
| throw new Error("Invalid tool call payload"); | ||
| } | ||
| const session = this.sessions.get(params.sessionId); | ||
| if (!session) { | ||
| throw new Error(`Unknown session ${params.sessionId}`); | ||
| } | ||
| const handler = session.getToolHandler(params.toolName); | ||
| if (!handler) { | ||
| return { result: this.buildUnsupportedToolResult(params.toolName) }; | ||
| } | ||
| return await this.executeToolCall(handler, params); | ||
| } | ||
| async executeToolCall(handler, request) { | ||
| try { | ||
| const invocation = { | ||
| sessionId: request.sessionId, | ||
| toolCallId: request.toolCallId, | ||
| toolName: request.toolName, | ||
| arguments: request.arguments | ||
| }; | ||
| const result = await handler(request.arguments, invocation); | ||
| return { result: this.normalizeToolResult(result) }; | ||
| } catch (error) { | ||
| const message = error instanceof Error ? error.message : String(error); | ||
| return { | ||
| result: { | ||
| // Don't expose detailed error information to the LLM for security reasons | ||
| textResultForLlm: "Invoking this tool produced an error. Detailed information is not available.", | ||
| resultType: "failure", | ||
| error: message, | ||
| toolTelemetry: {} | ||
| } | ||
| }; | ||
| } | ||
| } | ||
| normalizeToolResult(result) { | ||
| if (result === void 0 || result === null) { | ||
| return { | ||
| textResultForLlm: "Tool returned no result", | ||
| resultType: "failure", | ||
| error: "tool returned no result", | ||
| toolTelemetry: {} | ||
| }; | ||
| } | ||
| if (this.isToolResultObject(result)) { | ||
| return result; | ||
| } | ||
| const textResult = typeof result === "string" ? result : JSON.stringify(result); | ||
| return { | ||
| textResultForLlm: textResult, | ||
| resultType: "success", | ||
| toolTelemetry: {} | ||
| }; | ||
| } | ||
| isToolResultObject(value) { | ||
| return typeof value === "object" && value !== null && "textResultForLlm" in value && typeof value.textResultForLlm === "string" && "resultType" in value; | ||
| } | ||
| buildUnsupportedToolResult(toolName) { | ||
| return { | ||
| textResultForLlm: `Tool '${toolName}' is not supported by this client instance.`, | ||
| resultType: "failure", | ||
| error: `tool '${toolName}' not supported`, | ||
| toolTelemetry: {} | ||
| }; | ||
| } | ||
| /** | ||
| * Attempt to reconnect to the server | ||
| */ | ||
| async reconnect() { | ||
| this.state = "disconnected"; | ||
| try { | ||
| await this.stop(); | ||
| await this.start(); | ||
| } catch (_error) { | ||
| } | ||
| } | ||
| } | ||
| export { | ||
| CopilotClient | ||
| }; |
| /** | ||
| * AUTO-GENERATED FILE - DO NOT EDIT | ||
| * | ||
| * Generated from: @github/copilot/session-events.schema.json | ||
| * Generated by: scripts/generate-session-types.ts | ||
| * Generated at: 2026-01-07T09:10:52.771Z | ||
| * | ||
| * To update these types: | ||
| * 1. Update the schema in copilot-agent-runtime | ||
| * 2. Run: npm run generate:session-types | ||
| */ | ||
| export type SessionEvent = { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "session.start"; | ||
| data: { | ||
| sessionId: string; | ||
| version: number; | ||
| producer: string; | ||
| copilotVersion: string; | ||
| startTime: string; | ||
| selectedModel?: string; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "session.resume"; | ||
| data: { | ||
| resumeTime: string; | ||
| eventCount: number; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "session.error"; | ||
| data: { | ||
| errorType: string; | ||
| message: string; | ||
| stack?: string; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral: true; | ||
| type: "session.idle"; | ||
| data: {}; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "session.info"; | ||
| data: { | ||
| infoType: string; | ||
| message: string; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "session.model_change"; | ||
| data: { | ||
| previousModel?: string; | ||
| newModel: string; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "session.handoff"; | ||
| data: { | ||
| handoffTime: string; | ||
| sourceType: "remote" | "local"; | ||
| repository?: { | ||
| owner: string; | ||
| name: string; | ||
| branch?: string; | ||
| }; | ||
| context?: string; | ||
| summary?: string; | ||
| remoteSessionId?: string; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "session.truncation"; | ||
| data: { | ||
| tokenLimit: number; | ||
| preTruncationTokensInMessages: number; | ||
| preTruncationMessagesLength: number; | ||
| postTruncationTokensInMessages: number; | ||
| postTruncationMessagesLength: number; | ||
| tokensRemovedDuringTruncation: number; | ||
| messagesRemovedDuringTruncation: number; | ||
| performedBy: string; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "user.message"; | ||
| data: { | ||
| content: string; | ||
| transformedContent?: string; | ||
| attachments?: { | ||
| type: "file" | "directory"; | ||
| path: string; | ||
| displayName: string; | ||
| }[]; | ||
| source?: string; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "assistant.turn_start"; | ||
| data: { | ||
| turnId: string; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral: true; | ||
| type: "assistant.intent"; | ||
| data: { | ||
| intent: string; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "assistant.message"; | ||
| data: { | ||
| messageId: string; | ||
| content: string; | ||
| chunkContent?: string; | ||
| totalResponseSizeBytes?: number; | ||
| toolRequests?: { | ||
| toolCallId: string; | ||
| name: string; | ||
| arguments?: unknown; | ||
| }[]; | ||
| parentToolCallId?: string; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "assistant.turn_end"; | ||
| data: { | ||
| turnId: string; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral: true; | ||
| type: "assistant.usage"; | ||
| data: { | ||
| model?: string; | ||
| inputTokens?: number; | ||
| outputTokens?: number; | ||
| cacheReadTokens?: number; | ||
| cacheWriteTokens?: number; | ||
| cost?: number; | ||
| duration?: number; | ||
| initiator?: string; | ||
| apiCallId?: string; | ||
| providerCallId?: string; | ||
| quotaSnapshots?: { | ||
| [k: string]: { | ||
| isUnlimitedEntitlement: boolean; | ||
| entitlementRequests: number; | ||
| usedRequests: number; | ||
| usageAllowedWithExhaustedQuota: boolean; | ||
| overage: number; | ||
| overageAllowedWithExhaustedQuota: boolean; | ||
| remainingPercentage: number; | ||
| resetDate?: string; | ||
| }; | ||
| }; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "abort"; | ||
| data: { | ||
| reason: string; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "tool.user_requested"; | ||
| data: { | ||
| toolCallId: string; | ||
| toolName: string; | ||
| arguments?: unknown; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "tool.execution_start"; | ||
| data: { | ||
| toolCallId: string; | ||
| toolName: string; | ||
| arguments?: unknown; | ||
| parentToolCallId?: string; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral: true; | ||
| type: "tool.execution_partial_result"; | ||
| data: { | ||
| toolCallId: string; | ||
| partialOutput: string; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "tool.execution_complete"; | ||
| data: { | ||
| toolCallId: string; | ||
| success: boolean; | ||
| isUserRequested?: boolean; | ||
| result?: { | ||
| content: string; | ||
| }; | ||
| error?: { | ||
| message: string; | ||
| code?: string; | ||
| }; | ||
| toolTelemetry?: { | ||
| [k: string]: unknown; | ||
| }; | ||
| parentToolCallId?: string; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "custom_agent.started"; | ||
| data: { | ||
| toolCallId: string; | ||
| agentName: string; | ||
| agentDisplayName: string; | ||
| agentDescription: string; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "custom_agent.completed"; | ||
| data: { | ||
| toolCallId: string; | ||
| agentName: string; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "custom_agent.failed"; | ||
| data: { | ||
| toolCallId: string; | ||
| agentName: string; | ||
| error: string; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "custom_agent.selected"; | ||
| data: { | ||
| agentName: string; | ||
| agentDisplayName: string; | ||
| tools: string[] | null; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "hook.start"; | ||
| data: { | ||
| hookInvocationId: string; | ||
| hookType: string; | ||
| input?: unknown; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "hook.end"; | ||
| data: { | ||
| hookInvocationId: string; | ||
| hookType: string; | ||
| output?: unknown; | ||
| success: boolean; | ||
| error?: { | ||
| message: string; | ||
| stack?: string; | ||
| }; | ||
| }; | ||
| } | { | ||
| id: string; | ||
| timestamp: string; | ||
| parentId: string | null; | ||
| ephemeral?: boolean; | ||
| type: "system.message"; | ||
| data: { | ||
| content: string; | ||
| role: "system" | "developer"; | ||
| name?: string; | ||
| metadata?: { | ||
| promptVersion?: string; | ||
| variables?: { | ||
| [k: string]: unknown; | ||
| }; | ||
| }; | ||
| }; | ||
| }; |
| /** | ||
| * Copilot SDK - TypeScript/Node.js Client | ||
| * | ||
| * JSON-RPC based SDK for programmatic control of GitHub Copilot CLI | ||
| */ | ||
| export { CopilotClient } from "./client.js"; | ||
| export { CopilotSession } from "./session.js"; | ||
| export { defineTool } from "./types.js"; | ||
| export type { ConnectionState, CopilotClientOptions, MessageOptions, ResumeSessionConfig, SessionConfig, SessionEvent, SessionEventHandler, SessionMetadata, SystemMessageAppendConfig, SystemMessageConfig, SystemMessageReplaceConfig, Tool, ToolHandler, ToolInvocation, ToolResultObject, ZodSchema, } from "./types.js"; |
| import { CopilotClient } from "./client.js"; | ||
| import { CopilotSession } from "./session.js"; | ||
| import { defineTool } from "./types.js"; | ||
| export { | ||
| CopilotClient, | ||
| CopilotSession, | ||
| defineTool | ||
| }; |
| /** | ||
| * Copilot Session - represents a single conversation session with the CLI | ||
| */ | ||
| import type { MessageConnection } from "vscode-jsonrpc/node"; | ||
| import type { MessageOptions, SessionEvent, SessionEventHandler, Tool, ToolHandler } from "./types.js"; | ||
| export declare class CopilotSession { | ||
| readonly sessionId: string; | ||
| private connection; | ||
| private eventHandlers; | ||
| private toolHandlers; | ||
| constructor(sessionId: string, connection: MessageConnection); | ||
| /** | ||
| * Send a message to this session | ||
| */ | ||
| send(options: MessageOptions): Promise<string>; | ||
| /** | ||
| * Subscribe to events from this session | ||
| * @returns Unsubscribe function | ||
| */ | ||
| on(handler: SessionEventHandler): () => void; | ||
| /** | ||
| * Internal: dispatch an event to all handlers | ||
| */ | ||
| _dispatchEvent(event: SessionEvent): void; | ||
| registerTools(tools?: Tool[]): void; | ||
| getToolHandler(name: string): ToolHandler | undefined; | ||
| /** | ||
| * Get all events/messages from this session | ||
| */ | ||
| getMessages(): Promise<SessionEvent[]>; | ||
| /** | ||
| * Destroy this session and free resources | ||
| */ | ||
| destroy(): Promise<void>; | ||
| /** | ||
| * Abort the currently processing message in this session | ||
| */ | ||
| abort(): Promise<void>; | ||
| } |
| class CopilotSession { | ||
| constructor(sessionId, connection) { | ||
| this.sessionId = sessionId; | ||
| this.connection = connection; | ||
| } | ||
| eventHandlers = /* @__PURE__ */ new Set(); | ||
| toolHandlers = /* @__PURE__ */ new Map(); | ||
| /** | ||
| * Send a message to this session | ||
| */ | ||
| async send(options) { | ||
| const response = await this.connection.sendRequest("session.send", { | ||
| sessionId: this.sessionId, | ||
| prompt: options.prompt, | ||
| attachments: options.attachments, | ||
| mode: options.mode | ||
| }); | ||
| return response.messageId; | ||
| } | ||
| /** | ||
| * Subscribe to events from this session | ||
| * @returns Unsubscribe function | ||
| */ | ||
| on(handler) { | ||
| this.eventHandlers.add(handler); | ||
| return () => { | ||
| this.eventHandlers.delete(handler); | ||
| }; | ||
| } | ||
| /** | ||
| * Internal: dispatch an event to all handlers | ||
| */ | ||
| _dispatchEvent(event) { | ||
| for (const handler of this.eventHandlers) { | ||
| try { | ||
| handler(event); | ||
| } catch (_error) { | ||
| } | ||
| } | ||
| } | ||
| registerTools(tools) { | ||
| this.toolHandlers.clear(); | ||
| if (!tools) { | ||
| return; | ||
| } | ||
| for (const tool of tools) { | ||
| this.toolHandlers.set(tool.name, tool.handler); | ||
| } | ||
| } | ||
| getToolHandler(name) { | ||
| return this.toolHandlers.get(name); | ||
| } | ||
| /** | ||
| * Get all events/messages from this session | ||
| */ | ||
| async getMessages() { | ||
| const response = await this.connection.sendRequest("session.getMessages", { | ||
| sessionId: this.sessionId | ||
| }); | ||
| return response.events; | ||
| } | ||
| /** | ||
| * Destroy this session and free resources | ||
| */ | ||
| async destroy() { | ||
| await this.connection.sendRequest("session.destroy", { | ||
| sessionId: this.sessionId | ||
| }); | ||
| this.eventHandlers.clear(); | ||
| this.toolHandlers.clear(); | ||
| } | ||
| /** | ||
| * Abort the currently processing message in this session | ||
| */ | ||
| async abort() { | ||
| await this.connection.sendRequest("session.abort", { | ||
| sessionId: this.sessionId | ||
| }); | ||
| } | ||
| } | ||
| export { | ||
| CopilotSession | ||
| }; |
+269
| /** | ||
| * Type definitions for the Copilot SDK | ||
| */ | ||
| import type { SessionEvent as GeneratedSessionEvent } from "./generated/session-events.js"; | ||
| export type SessionEvent = GeneratedSessionEvent; | ||
| /** | ||
| * Options for creating a CopilotClient | ||
| */ | ||
| export interface CopilotClientOptions { | ||
| /** | ||
| * Path to the Copilot CLI executable | ||
| * @default "copilot" (searches PATH) | ||
| */ | ||
| cliPath?: string; | ||
| /** | ||
| * Extra arguments to pass to the CLI executable (inserted before SDK-managed args) | ||
| */ | ||
| cliArgs?: string[]; | ||
| /** | ||
| * Working directory for the CLI process | ||
| * If not set, inherits the current process's working directory | ||
| */ | ||
| cwd?: string; | ||
| /** | ||
| * Port for the CLI server (TCP mode only) | ||
| * @default 0 (random available port) | ||
| */ | ||
| port?: number; | ||
| /** | ||
| * Use stdio transport instead of TCP | ||
| * When true, communicates with CLI via stdin/stdout pipes | ||
| * @default true | ||
| */ | ||
| useStdio?: boolean; | ||
| /** | ||
| * URL of an existing Copilot CLI server to connect to over TCP | ||
| * When provided, the client will not spawn a CLI process | ||
| * Format: "host:port" or "http://host:port" or just "port" (defaults to localhost) | ||
| * Examples: "localhost:8080", "http://127.0.0.1:9000", "8080" | ||
| * Mutually exclusive with cliPath, useStdio | ||
| */ | ||
| cliUrl?: string; | ||
| /** | ||
| * Log level for the CLI server | ||
| */ | ||
| logLevel?: "none" | "error" | "warning" | "info" | "debug" | "all"; | ||
| /** | ||
| * Auto-start the CLI server on first use | ||
| * @default true | ||
| */ | ||
| autoStart?: boolean; | ||
| /** | ||
| * Auto-restart the CLI server if it crashes | ||
| * @default true | ||
| */ | ||
| autoRestart?: boolean; | ||
| /** | ||
| * Environment variables to pass to the CLI process. If not set, inherits process.env. | ||
| */ | ||
| env?: Record<string, string | undefined>; | ||
| } | ||
| /** | ||
| * Configuration for creating a session | ||
| */ | ||
| export type ToolResultType = "success" | "failure" | "rejected" | "denied"; | ||
| export type ToolBinaryResult = { | ||
| data: string; | ||
| mimeType: string; | ||
| type: string; | ||
| description?: string; | ||
| }; | ||
| export type ToolResultObject = { | ||
| textResultForLlm: string; | ||
| binaryResultsForLlm?: ToolBinaryResult[]; | ||
| resultType: ToolResultType; | ||
| error?: string; | ||
| sessionLog?: string; | ||
| toolTelemetry?: Record<string, unknown>; | ||
| }; | ||
| export type ToolResult = string | ToolResultObject; | ||
| export interface ToolInvocation { | ||
| sessionId: string; | ||
| toolCallId: string; | ||
| toolName: string; | ||
| arguments: unknown; | ||
| } | ||
| export type ToolHandler<TArgs = unknown> = (args: TArgs, invocation: ToolInvocation) => Promise<unknown> | unknown; | ||
| /** | ||
| * Zod-like schema interface for type inference. | ||
| * Any object with `toJSONSchema()` method is treated as a Zod schema. | ||
| */ | ||
| export interface ZodSchema<T = unknown> { | ||
| _output: T; | ||
| toJSONSchema(): Record<string, unknown>; | ||
| } | ||
| /** | ||
| * Tool definition. Parameters can be either: | ||
| * - A Zod schema (provides type inference for handler) | ||
| * - A raw JSON schema object | ||
| * - Omitted (no parameters) | ||
| */ | ||
| export interface Tool<TArgs = unknown> { | ||
| name: string; | ||
| description?: string; | ||
| parameters?: ZodSchema<TArgs> | Record<string, unknown>; | ||
| handler: ToolHandler<TArgs>; | ||
| } | ||
| /** | ||
| * Helper to define a tool with Zod schema and get type inference for the handler. | ||
| * Without this helper, TypeScript cannot infer handler argument types from Zod schemas. | ||
| */ | ||
| export declare function defineTool<T = unknown>(name: string, config: { | ||
| description?: string; | ||
| parameters?: ZodSchema<T> | Record<string, unknown>; | ||
| handler: ToolHandler<T>; | ||
| }): Tool<T>; | ||
| export interface ToolCallRequestPayload { | ||
| sessionId: string; | ||
| toolCallId: string; | ||
| toolName: string; | ||
| arguments: unknown; | ||
| } | ||
| export interface ToolCallResponsePayload { | ||
| result: ToolResult; | ||
| } | ||
| /** | ||
| * Append mode: Use CLI foundation with optional appended content (default). | ||
| */ | ||
| export interface SystemMessageAppendConfig { | ||
| mode?: "append"; | ||
| /** | ||
| * Additional instructions appended after SDK-managed sections. | ||
| */ | ||
| content?: string; | ||
| } | ||
| /** | ||
| * Replace mode: Use caller-provided system message entirely. | ||
| * Removes all SDK guardrails including security restrictions. | ||
| */ | ||
| export interface SystemMessageReplaceConfig { | ||
| mode: "replace"; | ||
| /** | ||
| * Complete system message content. | ||
| * Replaces the entire SDK-managed system message. | ||
| */ | ||
| content: string; | ||
| } | ||
| /** | ||
| * System message configuration for session creation. | ||
| * - Append mode (default): SDK foundation + optional custom content | ||
| * - Replace mode: Full control, caller provides entire system message | ||
| */ | ||
| export type SystemMessageConfig = SystemMessageAppendConfig | SystemMessageReplaceConfig; | ||
| export interface SessionConfig { | ||
| /** | ||
| * Optional custom session ID | ||
| * If not provided, server will generate one | ||
| */ | ||
| sessionId?: string; | ||
| /** | ||
| * Model to use for this session | ||
| */ | ||
| model?: string; | ||
| /** | ||
| * Tools exposed to the CLI server | ||
| */ | ||
| tools?: Tool<any>[]; | ||
| /** | ||
| * System message configuration | ||
| * Controls how the system prompt is constructed | ||
| */ | ||
| systemMessage?: SystemMessageConfig; | ||
| /** | ||
| * List of tool names to allow. When specified, only these tools will be available. | ||
| * Takes precedence over excludedTools. | ||
| */ | ||
| availableTools?: string[]; | ||
| /** | ||
| * List of tool names to disable. All other tools remain available. | ||
| * Ignored if availableTools is specified. | ||
| */ | ||
| excludedTools?: string[]; | ||
| /** | ||
| * Custom provider configuration (BYOK - Bring Your Own Key). | ||
| * When specified, uses the provided API endpoint instead of the Copilot API. | ||
| */ | ||
| provider?: ProviderConfig; | ||
| } | ||
| /** | ||
| * Configuration for resuming a session | ||
| */ | ||
| export type ResumeSessionConfig = Pick<SessionConfig, "tools" | "provider">; | ||
| /** | ||
| * Configuration for a custom API provider. | ||
| */ | ||
| export interface ProviderConfig { | ||
| /** | ||
| * Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. | ||
| */ | ||
| type?: "openai" | "azure" | "anthropic"; | ||
| /** | ||
| * API format (openai/azure only). Defaults to "completions". | ||
| */ | ||
| wireApi?: "completions" | "responses"; | ||
| /** | ||
| * API endpoint URL | ||
| */ | ||
| baseUrl: string; | ||
| /** | ||
| * API key. Optional for local providers like Ollama. | ||
| */ | ||
| apiKey?: string; | ||
| /** | ||
| * Bearer token for authentication. Sets the Authorization header directly. | ||
| * Use this for services requiring bearer token auth instead of API key. | ||
| * Takes precedence over apiKey when both are set. | ||
| */ | ||
| bearerToken?: string; | ||
| /** | ||
| * Azure-specific options | ||
| */ | ||
| azure?: { | ||
| /** | ||
| * API version. Defaults to "2024-10-21". | ||
| */ | ||
| apiVersion?: string; | ||
| }; | ||
| } | ||
| /** | ||
| * Options for sending a message to a session | ||
| */ | ||
| export interface MessageOptions { | ||
| /** | ||
| * The prompt/message to send | ||
| */ | ||
| prompt: string; | ||
| /** | ||
| * File or directory attachments | ||
| */ | ||
| attachments?: Array<{ | ||
| type: "file" | "directory"; | ||
| path: string; | ||
| displayName?: string; | ||
| }>; | ||
| /** | ||
| * Message delivery mode | ||
| * - "enqueue": Add to queue (default) | ||
| * - "immediate": Send immediately | ||
| */ | ||
| mode?: "enqueue" | "immediate"; | ||
| } | ||
| /** | ||
| * Event handler callback type | ||
| */ | ||
| export type SessionEventHandler = (event: SessionEvent) => void; | ||
| /** | ||
| * Connection state | ||
| */ | ||
| export type ConnectionState = "disconnected" | "connecting" | "connected" | "error"; | ||
| /** | ||
| * Metadata about a session | ||
| */ | ||
| export interface SessionMetadata { | ||
| sessionId: string; | ||
| startTime: Date; | ||
| modifiedTime: Date; | ||
| summary?: string; | ||
| isRemote: boolean; | ||
| } |
| function defineTool(name, config) { | ||
| return { name, ...config }; | ||
| } | ||
| export { | ||
| defineTool | ||
| }; |
+296
| # Copilot SDK for Node.js/TypeScript | ||
| TypeScript SDK for programmatic control of GitHub Copilot CLI via JSON-RPC. | ||
| ## Installation | ||
| ```bash | ||
| npm install @github/copilot-sdk | ||
| ``` | ||
| ## Quick Start | ||
| ```typescript | ||
| import { CopilotClient } from "@github/copilot-sdk"; | ||
| // Create and start client | ||
| const client = new CopilotClient(); | ||
| await client.start(); | ||
| // Create a session | ||
| const session = await client.createSession({ | ||
| model: "gpt-5" | ||
| }); | ||
| // Listen to events | ||
| session.on((event) => { | ||
| if (event.type === "assistant.message") { | ||
| console.log(event.data.content); | ||
| } | ||
| }); | ||
| // Send a message | ||
| await session.send({ | ||
| prompt: "What is 2+2?" | ||
| }); | ||
| // Clean up | ||
| await session.destroy(); | ||
| await client.stop(); | ||
| ``` | ||
| ## API Reference | ||
| ### CopilotClient | ||
| #### Constructor | ||
| ```typescript | ||
| new CopilotClient(options?: CopilotClientOptions) | ||
| ``` | ||
| **Options:** | ||
| - `cliPath?: string` - Path to CLI executable (default: "copilot" from PATH) | ||
| - `cliArgs?: string[]` - Extra arguments prepended before SDK-managed flags (e.g. `["./dist-cli/index.js"]` when using `node`) | ||
| - `cliUrl?: string` - URL of existing CLI server to connect to (e.g., `"localhost:8080"`, `"http://127.0.0.1:9000"`, or just `"8080"`). When provided, the client will not spawn a CLI process. Mutually exclusive with `cliPath` and `useStdio`. | ||
| - `port?: number` - Server port (default: 0 for random) | ||
| - `useStdio?: boolean` - Use stdio transport (default: true) | ||
| - `logLevel?: string` - Log level (default: "info") | ||
| - `autoStart?: boolean` - Auto-start server (default: true) | ||
| - `autoRestart?: boolean` - Auto-restart on crash (default: true) | ||
| #### Methods | ||
| ##### `start(): Promise<void>` | ||
| Start the CLI server and establish connection. | ||
| ##### `stop(): Promise<Error[]>` | ||
| Stop the server and close all sessions. Returns a list of any errors encountered during cleanup. | ||
| ##### `forceStop(): Promise<void>` | ||
| Force stop the CLI server without graceful cleanup. Use when `stop()` takes too long. | ||
| ##### `createSession(config?: SessionConfig): Promise<CopilotSession>` | ||
| Create a new conversation session. | ||
| **Config:** | ||
| - `sessionId?: string` - Custom session ID | ||
| - `model?: string` - Model to use ("gpt-5", "claude-sonnet-4.5", etc.) | ||
| - `tools?: Tool[]` - Custom tools exposed to the CLI | ||
| - `systemMessage?: SystemMessageConfig` - System message customization (see below) | ||
| ##### `resumeSession(sessionId: string, config?: ResumeSessionConfig): Promise<CopilotSession>` | ||
| Resume an existing session. | ||
| ##### `ping(message?: string): Promise<{ message: string; timestamp: number }>` | ||
| Ping the server to check connectivity. | ||
| ##### `getState(): ConnectionState` | ||
| Get current connection state. | ||
| ##### `listSessions(): Promise<SessionMetadata[]>` | ||
| List all available sessions. | ||
| ##### `deleteSession(sessionId: string): Promise<void>` | ||
| Delete a session and its data from disk. | ||
| --- | ||
| ### CopilotSession | ||
| Represents a single conversation session. | ||
| #### Methods | ||
| ##### `send(options: MessageOptions): Promise<string>` | ||
| Send a message to the session. | ||
| **Options:** | ||
| - `prompt: string` - The message/prompt to send | ||
| - `attachments?: Array<{type, path, displayName}>` - File attachments | ||
| - `mode?: "enqueue" | "immediate"` - Delivery mode | ||
| Returns the message ID. | ||
| ##### `on(handler: SessionEventHandler): () => void` | ||
| Subscribe to session events. Returns an unsubscribe function. | ||
| ```typescript | ||
| const unsubscribe = session.on((event) => { | ||
| console.log(event); | ||
| }); | ||
| // Later... | ||
| unsubscribe(); | ||
| ``` | ||
| ##### `abort(): Promise<void>` | ||
| Abort the currently processing message in this session. | ||
| ##### `getMessages(): Promise<SessionEvent[]>` | ||
| Get all events/messages from this session. | ||
| ##### `destroy(): Promise<void>` | ||
| Destroy the session and free resources. | ||
| --- | ||
| ## Event Types | ||
| Sessions emit various events during processing: | ||
| - `user.message` - User message added | ||
| - `assistant.message` - Assistant response | ||
| - `assistant.message_chunk` - Streaming response chunk | ||
| - `tool.execution_start` - Tool execution started | ||
| - `tool.execution_end` - Tool execution completed | ||
| - And more... | ||
| See `SessionEvent` type in the source for full details. | ||
| ## Advanced Usage | ||
| ### Manual Server Control | ||
| ```typescript | ||
| const client = new CopilotClient({ autoStart: false }); | ||
| // Start manually | ||
| await client.start(); | ||
| // Use client... | ||
| // Stop manually | ||
| await client.stop(); | ||
| ``` | ||
| ### Tools | ||
| You can let the CLI call back into your process when the model needs capabilities you own. Use `defineTool` with Zod schemas for type-safe tool definitions: | ||
| ```ts | ||
| import { z } from "zod"; | ||
| import { CopilotClient, defineTool } from "@github/copilot-sdk"; | ||
| const session = await client.createSession({ | ||
| model: "gpt-5", | ||
| tools: [ | ||
| defineTool("lookup_issue", { | ||
| description: "Fetches issue details from our internal tracker", | ||
| parameters: z.object({ | ||
| id: z.string().describe("Issue identifier"), | ||
| }), | ||
| handler: async ({ id }) => { | ||
| const issue = await fetchIssue(id); | ||
| return issue; | ||
| }, | ||
| }), | ||
| ], | ||
| }); | ||
| ``` | ||
| When Copilot invokes `lookup_issue`, the client automatically runs your handler and responds to the CLI. Handlers can return any JSON-serializable value (automatically wrapped), a simple string, or a `ToolResultObject` for full control over result metadata. Raw JSON schemas are also supported if Zod isn't desired. | ||
| ### System Message Customization | ||
| Control the system prompt using `systemMessage` in session config: | ||
| ```typescript | ||
| const session = await client.createSession({ | ||
| model: "gpt-5", | ||
| systemMessage: { | ||
| content: ` | ||
| <workflow_rules> | ||
| - Always check for security vulnerabilities | ||
| - Suggest performance improvements when applicable | ||
| </workflow_rules> | ||
| `, | ||
| }, | ||
| }); | ||
| ``` | ||
| The SDK auto-injects environment context, tool instructions, and security guardrails. The default CLI persona is preserved, and your `content` is appended after SDK-managed sections. To change the persona or fully redefine the prompt, use `mode: "replace"`. | ||
| For full control (removes all guardrails), use `mode: "replace"`: | ||
| ```typescript | ||
| const session = await client.createSession({ | ||
| model: "gpt-5", | ||
| systemMessage: { | ||
| mode: "replace", | ||
| content: "You are a helpful assistant.", | ||
| }, | ||
| }); | ||
| ``` | ||
| ### Multiple Sessions | ||
| ```typescript | ||
| const session1 = await client.createSession({ model: "gpt-5" }); | ||
| const session2 = await client.createSession({ model: "claude-sonnet-4.5" }); | ||
| // Both sessions are independent | ||
| await session1.send({ prompt: "Hello from session 1" }); | ||
| await session2.send({ prompt: "Hello from session 2" }); | ||
| ``` | ||
| ### Custom Session IDs | ||
| ```typescript | ||
| const session = await client.createSession({ | ||
| sessionId: "my-custom-session-id", | ||
| model: "gpt-5", | ||
| }); | ||
| ``` | ||
| ### File Attachments | ||
| ```typescript | ||
| await session.send({ | ||
| prompt: "Analyze this file", | ||
| attachments: [ | ||
| { | ||
| type: "file", | ||
| path: "/path/to/file.js", | ||
| displayName: "My File", | ||
| }, | ||
| ], | ||
| }); | ||
| ``` | ||
| ## Error Handling | ||
| ```typescript | ||
| try { | ||
| const session = await client.createSession(); | ||
| await session.send({ prompt: "Hello" }); | ||
| } catch (error) { | ||
| console.error("Error:", error.message); | ||
| } | ||
| ``` | ||
| ## Requirements | ||
| - Node.js >= 18.0.0 | ||
| - GitHub Copilot CLI installed and in PATH (or provide custom `cliPath`) | ||
| ## License | ||
| MIT |
+67
-2
@@ -1,5 +0,70 @@ | ||
| { | ||
| "name": "@github/copilot-sdk", | ||
| "version": "0.0.1" | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/github/copilot-sdk.git" | ||
| }, | ||
| "version": "0.0.2", | ||
| "description": "TypeScript SDK for programmatic control of GitHub Copilot CLI via JSON-RPC", | ||
| "main": "./dist/index.js", | ||
| "types": "./dist/index.d.ts", | ||
| "exports": { | ||
| ".": { | ||
| "import": "./dist/index.js", | ||
| "types": "./dist/index.d.ts" | ||
| } | ||
| }, | ||
| "type": "module", | ||
| "scripts": { | ||
| "clean": "rimraf --glob dist *.tgz", | ||
| "build": "tsx esbuild-copilotsdk-nodejs.ts", | ||
| "test": "vitest run", | ||
| "test:watch": "vitest", | ||
| "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" --ignore-path .prettierignore", | ||
| "format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\" --ignore-path .prettierignore", | ||
| "lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"", | ||
| "lint:fix": "eslint --fix \"src/**/*.ts\" \"test/**/*.ts\"", | ||
| "typecheck": "tsc --noEmit", | ||
| "generate:session-types": "tsx scripts/generate-session-types.ts", | ||
| "prepublishOnly": "npm run build", | ||
| "package": "npm run clean && npm run build && node scripts/set-version.js && npm pack && npm version 0.1.0 --no-git-tag-version --allow-same-version" | ||
| }, | ||
| "keywords": [ | ||
| "github", | ||
| "copilot", | ||
| "sdk", | ||
| "jsonrpc", | ||
| "agent" | ||
| ], | ||
| "author": "GitHub", | ||
| "license": "MIT", | ||
| "dependencies": { | ||
| "@github/copilot": "^0.0.375-1", | ||
| "vscode-jsonrpc": "^8.2.1", | ||
| "zod": "^4.3.5" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "^22.0.0", | ||
| "@typescript-eslint/eslint-plugin": "^8.0.0", | ||
| "@typescript-eslint/parser": "^8.0.0", | ||
| "esbuild": "^0.27.0", | ||
| "eslint": "^9.0.0", | ||
| "glob": "^11.0.0", | ||
| "json-schema": "^0.4.0", | ||
| "json-schema-to-typescript": "^15.0.4", | ||
| "prettier": "^3.4.0", | ||
| "quicktype-core": "^23.2.6", | ||
| "rimraf": "^6.1.2", | ||
| "semver": "^7.7.3", | ||
| "tsx": "^4.20.6", | ||
| "typescript": "^5.0.0", | ||
| "vitest": "^4.0.16" | ||
| }, | ||
| "engines": { | ||
| "node": ">=18.0.0" | ||
| }, | ||
| "files": [ | ||
| "dist/**/*", | ||
| "README.md" | ||
| ] | ||
| } |
Unpublished package
Supply chain riskPackage version was not found on the registry. It may exist on a different registry and need to be configured to pull from that registry.
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Empty package
Supply chain riskPackage does not contain any code. It may be removed, is name squatting, or the result of a faulty package publish.
No README
QualityPackage does not have a README. This may indicate a failed publish or a low quality package.
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
No License Found
LicenseLicense information could not be found.
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
48134
81483.05%12
1100%0
-100%1410
Infinity%1
-50%0
-100%297
Infinity%Yes
NaN3
Infinity%15
Infinity%2
100%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added