16 KiB
applyTo, description, name
| applyTo | description | name |
|---|---|---|
| **.ts, **.js, package.json | This file provides guidance on building Node.js/TypeScript applications using GitHub Copilot SDK. | GitHub Copilot SDK Node.js Instructions |
Core Principles
- The SDK is in technical preview and may have breaking changes
- Requires Node.js 18.0 or later
- Requires GitHub Copilot CLI installed and in PATH
- Built with TypeScript for type safety
- Uses async/await patterns throughout
- Provides full TypeScript type definitions
Installation
Always install via npm/pnpm/yarn:
npm install @github/copilot-sdk
# or
pnpm add @github/copilot-sdk
# or
yarn add @github/copilot-sdk
Client Initialization
Basic Client Setup
import { CopilotClient } from "@github/copilot-sdk";
const client = new CopilotClient();
await client.start();
// Use client...
await client.stop();
Client Configuration Options
When creating a CopilotClient, use CopilotClientOptions:
cliPath- Path to CLI executable (default: "copilot" from PATH)cliArgs- Extra arguments prepended before SDK-managed flags (string[])cliUrl- URL of existing CLI server (e.g., "localhost:8080"). When provided, client won't spawn a processport- Server port (default: 0 for random)useStdio- Use stdio transport instead of TCP (default: true)logLevel- Log level (default: "debug")autoStart- Auto-start server (default: true)autoRestart- Auto-restart on crash (default: true)cwd- Working directory for the CLI process (default: process.cwd())env- Environment variables for the CLI process (default: process.env)
Manual Server Control
For explicit control:
const client = new CopilotClient({ autoStart: false });
await client.start();
// Use client...
await client.stop();
Use forceStop() when stop() takes too long.
Session Management
Creating Sessions
Use SessionConfig for configuration:
const session = await client.createSession({
model: "gpt-5",
streaming: true,
tools: [...],
systemMessage: { ... },
availableTools: ["tool1", "tool2"],
excludedTools: ["tool3"],
provider: { ... }
});
Session Config Options
sessionId- Custom session ID (string)model- Model name ("gpt-5", "claude-sonnet-4.5", etc.)tools- Custom tools exposed to the CLI (Tool[])systemMessage- System message customization (SystemMessageConfig)availableTools- Allowlist of tool names (string[])excludedTools- Blocklist of tool names (string[])provider- Custom API provider configuration (BYOK) (ProviderConfig)streaming- Enable streaming response chunks (boolean)mcpServers- MCP server configurations (MCPServerConfig[])customAgents- Custom agent configurations (CustomAgentConfig[])configDir- Config directory override (string)skillDirectories- Skill directories (string[])disabledSkills- Disabled skills (string[])onPermissionRequest- Permission request handler (PermissionHandler)
Resuming Sessions
const session = await client.resumeSession("session-id", {
tools: [myNewTool],
});
Session Operations
session.sessionId- Get session identifier (string)await session.send({ prompt: "...", attachments: [...] })- Send message, returns Promiseawait session.sendAndWait({ prompt: "..." }, timeout)- Send and wait for idle, returns Promise<AssistantMessageEvent | null>await session.abort()- Abort current processingawait session.getMessages()- Get all events/messages, returns Promise<SessionEvent[]>await session.destroy()- Clean up session
Event Handling
Event Subscription Pattern
ALWAYS use async/await or Promises for waiting on session events:
await new Promise<void>((resolve) => {
session.on((event) => {
if (event.type === "assistant.message") {
console.log(event.data.content);
} else if (event.type === "session.idle") {
resolve();
}
});
session.send({ prompt: "..." });
});
Unsubscribing from Events
The on() method returns a function that unsubscribes:
const unsubscribe = session.on((event) => {
// handler
});
// Later...
unsubscribe();
Event Types
Use discriminated unions with type guards for event handling:
session.on((event) => {
switch (event.type) {
case "user.message":
// Handle user message
break;
case "assistant.message":
console.log(event.data.content);
break;
case "tool.executionStart":
// Tool execution started
break;
case "tool.executionComplete":
// Tool execution completed
break;
case "session.start":
// Session started
break;
case "session.idle":
// Session is idle (processing complete)
break;
case "session.error":
console.error(`Error: ${event.data.message}`);
break;
}
});
Streaming Responses
Enabling Streaming
Set streaming: true in SessionConfig:
const session = await client.createSession({
model: "gpt-5",
streaming: true,
});
Handling Streaming Events
Handle both delta events (incremental) and final events:
await new Promise<void>((resolve) => {
session.on((event) => {
switch (event.type) {
case "assistant.message.delta":
// Incremental text chunk
process.stdout.write(event.data.deltaContent);
break;
case "assistant.reasoning.delta":
// Incremental reasoning chunk (model-dependent)
process.stdout.write(event.data.deltaContent);
break;
case "assistant.message":
// Final complete message
console.log("\n--- Final ---");
console.log(event.data.content);
break;
case "assistant.reasoning":
// Final reasoning content
console.log("--- Reasoning ---");
console.log(event.data.content);
break;
case "session.idle":
resolve();
break;
}
});
session.send({ prompt: "Tell me a story" });
});
Note: Final events (assistant.message, assistant.reasoning) are ALWAYS sent regardless of streaming setting.
Custom Tools
Defining Tools with defineTool
Use defineTool for type-safe tool definitions:
import { defineTool } from "@github/copilot-sdk";
const session = await client.createSession({
model: "gpt-5",
tools: [
defineTool({
name: "lookup_issue",
description: "Fetch issue details from tracker",
parameters: {
type: "object",
properties: {
id: { type: "string", description: "Issue ID" },
},
required: ["id"],
},
handler: async (args) => {
const issue = await fetchIssue(args.id);
return issue;
},
}),
],
});
Using Zod for Parameters
The SDK supports Zod schemas for parameters:
import { z } from "zod";
const session = await client.createSession({
tools: [
defineTool({
name: "get_weather",
description: "Get weather for a location",
parameters: z.object({
location: z.string().describe("City name"),
units: z.enum(["celsius", "fahrenheit"]).optional(),
}),
handler: async (args) => {
return { temperature: 72, units: args.units || "fahrenheit" };
},
}),
],
});
Tool Return Types
- Return any JSON-serializable value (automatically wrapped)
- Or return
ToolResultObjectfor full control over metadata:
{
textResultForLlm: string; // Result shown to LLM
resultType: "success" | "failure";
error?: string; // Internal error (not shown to LLM)
toolTelemetry?: Record<string, unknown>;
}
Tool Execution Flow
When Copilot invokes a tool, the client automatically:
- Runs your handler function
- Serializes the return value
- Responds to the CLI
System Message Customization
Append Mode (Default - Preserves Guardrails)
const session = await client.createSession({
model: "gpt-5",
systemMessage: {
mode: "append",
content: `
<workflow_rules>
- Always check for security vulnerabilities
- Suggest performance improvements when applicable
</workflow_rules>
`,
},
});
Replace Mode (Full Control - Removes Guardrails)
const session = await client.createSession({
model: "gpt-5",
systemMessage: {
mode: "replace",
content: "You are a helpful assistant.",
},
});
File Attachments
Attach files to messages:
await session.send({
prompt: "Analyze this file",
attachments: [
{
type: "file",
path: "/path/to/file.ts",
displayName: "My File",
},
],
});
Message Delivery Modes
Use the mode property in message options:
"enqueue"- Queue message for processing"immediate"- Process message immediately
await session.send({
prompt: "...",
mode: "enqueue",
});
Multiple Sessions
Sessions are independent and can run concurrently:
const session1 = await client.createSession({ model: "gpt-5" });
const session2 = await client.createSession({ model: "claude-sonnet-4.5" });
await Promise.all([
session1.send({ prompt: "Hello from session 1" }),
session2.send({ prompt: "Hello from session 2" }),
]);
Bring Your Own Key (BYOK)
Use custom API providers via provider:
const session = await client.createSession({
provider: {
type: "openai",
baseUrl: "https://api.openai.com/v1",
apiKey: "your-api-key",
},
});
Session Lifecycle Management
Listing Sessions
const sessions = await client.listSessions();
for (const metadata of sessions) {
console.log(`${metadata.sessionId}: ${metadata.summary}`);
}
Deleting Sessions
await client.deleteSession(sessionId);
Getting Last Session ID
const lastId = await client.getLastSessionId();
if (lastId) {
const session = await client.resumeSession(lastId);
}
Checking Connection State
const state = client.getState();
// Returns: "disconnected" | "connecting" | "connected" | "error"
Error Handling
Standard Exception Handling
try {
const session = await client.createSession();
await session.send({ prompt: "Hello" });
} catch (error) {
console.error(`Error: ${error.message}`);
}
Session Error Events
Monitor session.error event type for runtime errors:
session.on((event) => {
if (event.type === "session.error") {
console.error(`Session Error: ${event.data.message}`);
}
});
Connectivity Testing
Use ping to verify server connectivity:
const response = await client.ping("health check");
console.log(`Server responded at ${new Date(response.timestamp)}`);
Resource Cleanup
Automatic Cleanup with Try-Finally
ALWAYS use try-finally or cleanup in a finally block:
const client = new CopilotClient();
try {
await client.start();
const session = await client.createSession();
try {
// Use session...
} finally {
await session.destroy();
}
} finally {
await client.stop();
}
Cleanup Function Pattern
async function withClient<T>(
fn: (client: CopilotClient) => Promise<T>,
): Promise<T> {
const client = new CopilotClient();
try {
await client.start();
return await fn(client);
} finally {
await client.stop();
}
}
async function withSession<T>(
client: CopilotClient,
fn: (session: CopilotSession) => Promise<T>,
): Promise<T> {
const session = await client.createSession();
try {
return await fn(session);
} finally {
await session.destroy();
}
}
// Usage
await withClient(async (client) => {
await withSession(client, async (session) => {
await session.send({ prompt: "Hello!" });
});
});
Best Practices
- Always use try-finally for resource cleanup
- Use Promises to wait for session.idle event
- Handle session.error events for robust error handling
- Use type guards or switch statements for event handling
- Enable streaming for better UX in interactive scenarios
- Use defineTool for type-safe tool definitions
- Use Zod schemas for runtime parameter validation
- Dispose event subscriptions when no longer needed
- Use systemMessage with mode: "append" to preserve safety guardrails
- Handle both delta and final events when streaming is enabled
- Leverage TypeScript types for compile-time safety
Common Patterns
Simple Query-Response
import { CopilotClient } from "@github/copilot-sdk";
const client = new CopilotClient();
try {
await client.start();
const session = await client.createSession({ model: "gpt-5" });
try {
await new Promise<void>((resolve) => {
session.on((event) => {
if (event.type === "assistant.message") {
console.log(event.data.content);
} else if (event.type === "session.idle") {
resolve();
}
});
session.send({ prompt: "What is 2+2?" });
});
} finally {
await session.destroy();
}
} finally {
await client.stop();
}
Multi-Turn Conversation
const session = await client.createSession();
async function sendAndWait(prompt: string): Promise<void> {
await new Promise<void>((resolve, reject) => {
const unsubscribe = session.on((event) => {
if (event.type === "assistant.message") {
console.log(event.data.content);
} else if (event.type === "session.idle") {
unsubscribe();
resolve();
} else if (event.type === "session.error") {
unsubscribe();
reject(new Error(event.data.message));
}
});
session.send({ prompt });
});
}
await sendAndWait("What is the capital of France?");
await sendAndWait("What is its population?");
SendAndWait Helper
// Use built-in sendAndWait for simpler synchronous interaction
const response = await session.sendAndWait({ prompt: "What is 2+2?" }, 60000);
if (response) {
console.log(response.data.content);
}
Tool with Type-Safe Parameters
import { z } from "zod";
import { defineTool } from "@github/copilot-sdk";
interface UserInfo {
id: string;
name: string;
email: string;
role: string;
}
const session = await client.createSession({
tools: [
defineTool({
name: "get_user",
description: "Retrieve user information",
parameters: z.object({
userId: z.string().describe("User ID"),
}),
handler: async (args): Promise<UserInfo> => {
return {
id: args.userId,
name: "John Doe",
email: "john@example.com",
role: "Developer",
};
},
}),
],
});
Streaming with Progress
let currentMessage = "";
const unsubscribe = session.on((event) => {
if (event.type === "assistant.message.delta") {
currentMessage += event.data.deltaContent;
process.stdout.write(event.data.deltaContent);
} else if (event.type === "assistant.message") {
console.log("\n\n=== Complete ===");
console.log(`Total length: ${event.data.content.length} chars`);
} else if (event.type === "session.idle") {
unsubscribe();
}
});
await session.send({ prompt: "Write a long story" });
Error Recovery
session.on((event) => {
if (event.type === "session.error") {
console.error("Session error:", event.data.message);
// Optionally retry or handle error
}
});
try {
await session.send({ prompt: "risky operation" });
} catch (error) {
// Handle send errors
console.error("Failed to send:", error);
}
TypeScript-Specific Features
Type Inference
import type { SessionEvent, AssistantMessageEvent } from "@github/copilot-sdk";
session.on((event: SessionEvent) => {
if (event.type === "assistant.message") {
// TypeScript knows event is AssistantMessageEvent here
const content: string = event.data.content;
}
});
Generic Helper
async function waitForEvent<T extends SessionEvent["type"]>(
session: CopilotSession,
eventType: T,
): Promise<Extract<SessionEvent, { type: T }>> {
return new Promise((resolve) => {
const unsubscribe = session.on((event) => {
if (event.type === eventType) {
unsubscribe();
resolve(event as Extract<SessionEvent, { type: T }>);
}
});
});
}
// Usage
const message = await waitForEvent(session, "assistant.message");
console.log(message.data.content);