🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@github/copilot-sdk

Package Overview
Dependencies
Maintainers
20
Versions
73
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@github/copilot-sdk - npm Package Compare versions

Package version was removed
This package version has been unpublished, mostly likely due to security reasons
Comparing version
0.0.1
to
0.0.2
+95
dist/client.d.ts
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;
}
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
};
/**
* 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
};
# 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"
]
}