copilot-sdk

GitHub

Multi-platform SDK for integrating GitHub Copilot Agent into apps and services

10,357 stars TypeScript Markdown Skills CodeWiki
AI Prompts & Endpoints
Agent Skills View CodeWiki Knowledge Base

Chapter: getting-started (docs/getting-started.md)

Build your first Copilot-powered app

In this tutorial, you'll use the Copilot SDK to build a command-line assistant. You'll start with the basics, add streaming responses, then add custom tools - giving Copilot the ability to call your code.

What you'll build:

text
You: What's the weather like in Seattle?
Copilot: Let me check the weather for Seattle...
Currently 62°F and cloudy with a chance of rain.
Typical Seattle weather!

You: How about Tokyo?
Copilot: In Tokyo it's 75°F and sunny. Great day to be outside!

Prerequisites

Before you begin, make sure you have:

* GitHub Copilot CLI installed and authenticated (the Node.js, Python, and .NET SDKs provide the CLI automatically—see Bundled CLI. Required for Go, Java, and Rust unless using their application-level CLI bundling features.)
* Your preferred language runtime:
* Node.js 20+ or Python 3.11+ or Go 1.24+ or Rust 1.94+ or Java 17+ or .NET 8.0+

Verify the CLI is working:

bash
copilot --version

Step 1: install the SDK

<details open>
<summary><strong>Node.js / TypeScript</strong></summary>

First, create a new directory and initialize your project:

bash
mkdir copilot-demo && cd copilot-demo
npm init -y --init-type module

Then install the SDK and TypeScript runner:

bash
npm install @github/copilot-sdk tsx

</details>

<details>
<summary><strong>Python</strong></summary>

bash
pip install github-copilot-sdk

</details>

<details>
<summary><strong>Go</strong></summary>

First, create a new directory and initialize your module:

bash
mkdir copilot-demo && cd copilot-demo
go mod init copilot-demo

Then install the SDK:

bash
go get github.com/github/copilot-sdk/go

</details>

<details>
<summary><strong>Rust</strong></summary>

First, create a new binary crate:

bash
cargo new copilot-demo && cd copilot-demo

Then install the SDK and direct dependencies used by the examples:

bash
cargo add github-copilot-sdk --features derive

Used by #[tokio::main] and tokio::spawn


cargo add tokio --features rt-multi-thread,macros

Used by custom-tool parameter derives later in this guide


cargo add serde --features derive
cargo add schemars

</details>

<details>
<summary><strong>.NET</strong></summary>

First, create a new console project:

bash
dotnet new console -n CopilotDemo && cd CopilotDemo

Then add the SDK:

bash
dotnet add package GitHub.Copilot.SDK

</details>

<details>
<summary><strong>Java</strong></summary>

First, create a new directory and initialize your project.

Maven—add to your pom.xml:

xml
<dependency>
<groupId>com.github</groupId>
<artifactId>copilot-sdk-java</artifactId>
<version>${copilot.sdk.version}</version>
</dependency>

Gradle—add to your build.gradle:

groovy
implementation 'com.github:copilot-sdk-java:${copilotSdkVersion}'

</details>

Step 2: send your first message

Create a new file and add the following code. This is the simplest way to use the SDK—about 5 lines of code.

<details open>
<summary><strong>Node.js / TypeScript</strong></summary>

Create index.ts:

typescript
import { CopilotClient } from "@github/copilot-sdk";

const client = new CopilotClient();
const session = await client.createSession({ model: "auto" });

const response = await session.sendAndWait({ prompt: "What is 2 + 2?" });
console.log(response?.data.content);

await client.stop();
process.exit(0);

Run it:

bash
npx tsx index.ts

</details>

<details>
<summary><strong>Python</strong></summary>

Create main.py:

python
import asyncio
from copilot import CopilotClient
from copilot.session import PermissionHandler

async def main():
client = CopilotClient()
await client.start()

session = await client.create_session(on_permission_request=PermissionHandler.approve_all, model="auto")
response = await session.send_and_wait("What is 2 + 2?")
print(response.data.content)

await client.stop()

asyncio.run(main())

Run it:

bash
python main.py

</details>

<details>
<summary><strong>Go</strong></summary>

Create main.go:

go
package main

import (
"context"
"fmt"
"log"
"os"

copilot "github.com/github/copilot-sdk/go"
)

func main() {
ctx := context.Background()
client := copilot.NewClient(nil)
if err := client.Start(ctx); err != nil {
log.Fatal(err)
}
defer client.Stop()

session, err := client.CreateSession(ctx, &copilot.SessionConfig{Model: "auto"})
if err != nil {
log.Fatal(err)
}

response, err := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "What is 2 + 2?"})
if err != nil {
log.Fatal(err)
}

if d, ok := response.Data.(*copilot.AssistantMessageData); ok {
fmt.Println(d.Content)
}
os.Exit(0)
}

Run it:

bash
go run main.go

</details>

<details>
<summary><strong>Rust</strong></summary>

Create src/main.rs:

rust
use std::sync::Arc;
use std::time::Duration;

use github_copilot_sdk::handler::ApproveAllHandler;
use github_copilot_sdk::{Client, ClientOptions, MessageOptions, SessionConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::start(ClientOptions::default()).await?;
let session = client
.create_session(SessionConfig::default().with_permission_handler(Arc::new(ApproveAllHandler)))
.await?;

let response = session
.send_and_wait(
MessageOptions::new("What is 2 + 2?").with_wait_timeout(Duration::from_secs(120)),
)
.await?;

if let Some(event) = response {
if let Some(content) = event.data.get("content").and_then(|value| value.as_str()) {
println!("{content}");
}
}

session.disconnect().await?;
client.stop().await?;
Ok(())
}

Run it:

bash
cargo run

</details>

<details>
<summary><strong>.NET</strong></summary>

Create a new console project and add this to Program.cs:

csharp
using GitHub.Copilot;

await using var client = new CopilotClient();
await using var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "auto",
OnPermissionRequest = PermissionHandler.ApproveAll
});

var response = await session.SendAndWaitAsync(new MessageOptions { Prompt = "What is 2 + 2?" });
Console.WriteLine(response?.Data.Content);

Run it:

bash
dotnet run

</details>

<details>
<summary><strong>Java</strong></summary>

Create HelloCopilot.java:


java
import com.github.copilot.CopilotClient;
import com.github.copilot.rpc.*;

public class HelloCopilot {
public static void main(String[] args) throws Exception {
try (var client = new CopilotClient()) {
client.start().get();

var session = client.createSession(
new SessionConfig()
.setModel("auto")
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();

var response = session.sendAndWait(
new MessageOptions().setPrompt("What is 2 + 2?")
).get();

System.out.println(response.getData().content());

client.stop().get();
}
}
}

Run it:

bash
javac -cp copilot-sdk.jar HelloCopilot.java && java -cp .:copilot-sdk.jar HelloCopilot

</details>

You should see:

text
4

Congratulations! You just built your first Copilot-powered app.

Step 3: add streaming responses

Right now, you wait for the complete response before seeing anything. Let's make it interactive by streaming the response as it's generated.

<details open>
<summary><strong>Node.js / TypeScript</strong></summary>

Update index.ts:

typescript
import { CopilotClient } from "@github/copilot-sdk";

const client = new CopilotClient();
const session = await client.createSession({
model: "auto",
streaming: true,
});

// Listen for response chunks
session.on("assistant.message_delta", (event) => {
process.stdout.write(event.data.deltaContent);
});
session.on("session.idle", () => {
console.log(); // New line when done
});

await session.sendAndWait({ prompt: "Tell me a short joke" });

await client.stop();
process.exit(0);

</details>

<details>
<summary><strong>Python</strong></summary>

Update main.py:

python
import asyncio
import sys
from copilot import CopilotClient
from copilot.session import PermissionHandler
from copilot.session_events import SessionEventType

async def main():
client = CopilotClient()
await client.start()

session = await client.create_session(on_permission_request=PermissionHandler.approve_all, model="auto", streaming=True)

# Listen for response chunks
def handle_event(event):
if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA:
sys.stdout.write(event.data.delta_content)
sys.stdout.flush()
if event.type == SessionEventType.SESSION_IDLE:
print() # New line when done

session.on(handle_event)

await session.send_and_wait("Tell me a short joke")

await client.stop()

asyncio.run(main())

</details>

<details>
<summary><strong>Go</strong></summary>

Update main.go:

go
package main

import (
"context"
"fmt"
"log"
"os"

copilot "github.com/github/copilot-sdk/go"
)

func main() {
ctx := context.Background()
client := copilot.NewClient(nil)
if err := client.Start(ctx); err != nil {
log.Fatal(err)
}
defer client.Stop()

session, err := client.CreateSession(ctx, &copilot.SessionConfig{
Model: "auto",
Streaming: copilot.Bool(true),
})
if err != nil {
log.Fatal(err)
}

// Listen for response chunks
session.On(func(event copilot.SessionEvent) {
switch d := event.Data.(type) {
case *copilot.AssistantMessageDeltaData:
fmt.Print(d.DeltaContent)
case *copilot.SessionIdleData:
_ = d
fmt.Println()
}
})

_, err = session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Tell me a short joke"})
if err != nil {
log.Fatal(err)
}
os.Exit(0)
}

</details>

<details>
<summary><strong>Rust</strong></summary>

Update src/main.rs:

rust
use std::io::{self, Write};
use std::sync::Arc;
use std::time::Duration;

use github_copilot_sdk::handler::ApproveAllHandler;
use github_copilot_sdk::{Client, ClientOptions, MessageOptions, SessionConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::start(ClientOptions::default()).await?;

let mut config = SessionConfig::default();
config.streaming = Some(true);
let session = client
.create_session(config.with_permission_handler(Arc::new(ApproveAllHandler)))
.await?;

// Listen for response chunks
let mut events = session.subscribe();
tokio::spawn(async move {
while let Ok(event) = events.recv().await {
match event.event_type.as_str() {
"assistant.message_delta" => {
if let Some(text) =
event.data.get("deltaContent").and_then(|value| value.as_str())
{
print!("{text}");
io::stdout().flush().ok();
}
}
"assistant.message" => println!(),
_ => {}
}
}
});

session
.send_and_wait(
MessageOptions::new("Tell me a short joke")
.with_wait_timeout(Duration::from_secs(120)),
)
.await?;

session.disconnect().await?;
client.stop().await?;
Ok(())
}

</details>

<details>
<summary><strong>.NET</strong></summary>

Update Program.cs:

csharp
using GitHub.Copilot;

await using var client = new CopilotClient();
await using var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "auto",
OnPermissionRequest = PermissionHandler.ApproveAll,
Streaming = true,
});

// Listen for response chunks
session.On<SessionEvent>(ev =>
{
if (ev is AssistantMessageDeltaEvent deltaEvent)
{
Console.Write(deltaEvent.Data.DeltaContent);
}
if (ev is SessionIdleEvent)
{
Console.WriteLine();
}
});

await session.SendAndWaitAsync(new MessageOptions { Prompt = "Tell me a short joke" });

</details>

<details>
<summary><strong>Java</strong></summary>

Update HelloCopilot.java:


java
import com.github.copilot.CopilotClient;
import com.github.copilot.rpc.*;

public class HelloCopilot {
public static void main(String[] args) throws Exception {
try (var client = new CopilotClient()) {
client.start().get();

var session = client.createSession(
new SessionConfig()
.setModel("auto")
.setStreaming(true)
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();

// Listen for response chunks
session.on(AssistantMessageDeltaEvent.class, delta -> {
System.out.print(delta.getData().deltaContent());
});
session.on(SessionIdleEvent.class, idle -> {
System.out.println(); // New line when done
});

session.sendAndWait(
new MessageOptions().setPrompt("Tell me a short joke")
).get();

client.stop().get();
}
}
}

</details>

Run the code again. You'll see the response appear word by word.

Event subscription methods

The SDK provides methods for subscribing to session events:

| Method | Description |
|--------|-------------|
| on(handler) | Subscribe to all events; returns unsubscribe function |
| on(eventType, handler) | Subscribe to specific event type (Node.js/TypeScript only); returns unsubscribe function |
| subscribe() | Subscribe to all events (Rust); filter by event_type |

<details open>
<summary><strong>Node.js / TypeScript</strong></summary>

typescript
// Subscribe to all events
const unsubscribeAll = session.on((event) => {
console.log("Event:", event.type);
});

// Subscribe to specific event type
const unsubscribeIdle = session.on("session.idle", (event) => {
console.log("Session is idle");
});

// Later, to unsubscribe:
unsubscribeAll();
unsubscribeIdle();

</details>

<details>
<summary><strong>Python</strong></summary>


python
from copilot import CopilotClient, PermissionDecisionApproveOnce
from copilot.session_events import SessionEvent, SessionEventType

client = CopilotClient()

session = await client.create_session(on_permission_request=lambda req, inv: PermissionDecisionApproveOnce())

Subscribe to all events


unsubscribe = session.on(lambda event: print(f"Event: {event.type}"))

Filter by event type in your handler


def handle_event(event: SessionEvent) -> None:
if event.type == SessionEventType.SESSION_IDLE:
print("Session is idle")
elif event.type == SessionEventType.ASSISTANT_MESSAGE:
print(f"Message: {event.data.content}")

unsubscribe = session.on(handle_event)

Later, to unsubscribe:


unsubscribe()


python

Subscribe to all events


unsubscribe = session.on(lambda event: print(f"Event: {event.type}"))

Filter by event type in your handler


def handle_event(event):
if event.type == SessionEventType.SESSION_IDLE:
print("Session is idle")
elif event.type == SessionEventType.ASSISTANT_MESSAGE:
print(f"Message: {event.data.content}")

unsubscribe = session.on(handle_event)

Later, to unsubscribe:


unsubscribe()

</details>

<details>
<summary><strong>Go</strong></summary>


go
package main

import (
"fmt"

copilot "github.com/github/copilot-sdk/go"
)

func main() {
session := &copilot.Session{}

// Subscribe to all events
unsubscribe := session.On(func(event copilot.SessionEvent) {
fmt.Println("Event:", event.Type)
})

// Filter by event type in your handler
session.On(func(event copilot.SessionEvent) {
switch d := event.Data.(type) {
case *copilot.SessionIdleData:
_ = d
fmt.Println("Session is idle")
case *copilot.AssistantMessageData:
fmt.Println("Message:", d.Content)
}
})

// Later, to unsubscribe:
unsubscribe()
}


go
// Subscribe to all events
unsubscribe := session.On(func(event copilot.SessionEvent) {
fmt.Println("Event:", event.Type)
})

// Filter by event type in your handler
session.On(func(event copilot.SessionEvent) {
switch d := event.Data.(type) {
case *copilot.SessionIdleData:
_ = d
fmt.Println("Session is idle")
case *copilot.AssistantMessageData:
fmt.Println("Message:", d.Content)
}
})

// Later, to unsubscribe:
unsubscribe()

</details>

<details>
<summary><strong>Rust</strong></summary>

rust
let mut events = session.subscribe();

tokio::spawn(async move {
while let Ok(event) = events.recv().await {
println!("Event: {}", event.event_type);

match event.event_type.as_str() {
"session.idle" => println!("Session is idle"),
"assistant.message" => {
if let Some(content) = event.data.get("content").and_then(|value| value.as_str()) {
println!("Message: {content}");
}
}
_ => {}
}
}
});

</details>

<details>
<summary><strong>.NET</strong></summary>


csharp
using GitHub.Copilot;

public static class EventSubscriptionExample
{
public static void Example(CopilotSession session)
{
// Subscribe to all events
var unsubscribe = session.On<SessionEvent>(ev => Console.WriteLine($"Event: {ev.Type}"));

// Filter by event type using pattern matching
session.On<SessionEvent>(ev =>
{
switch (ev)
{
case SessionIdleEvent:
Console.WriteLine("Session is idle");
break;
case AssistantMessageEvent msg:
Console.WriteLine($"Message: {msg.Data.Content}");
break;
}
});

// Later, to unsubscribe:
unsubscribe.Dispose();
}
}


csharp
// Subscribe to all events
var unsubscribe = session.On<SessionEvent>(ev => Console.WriteLine($"Event: {ev.Type}"));

// Filter by event type using pattern matching
session.On<SessionEvent>(ev =>
{
switch (ev)
{
case SessionIdleEvent:
Console.WriteLine("Session is idle");
break;
case AssistantMessageEvent msg:
Console.WriteLine($"Message: {msg.Data.Content}");
break;
}
});

// Later, to unsubscribe:
unsubscribe.Dispose();

</details>

<details>
<summary><strong>Java</strong></summary>


java
// Subscribe to all events
var unsubscribe = session.on(event -> {
System.out.println("Event: " + event.getType());
});

// Subscribe to a specific event type
session.on(AssistantMessageEvent.class, msg -> {
System.out.println("Message: " + msg.getData().content());
});

session.on(SessionIdleEvent.class, idle -> {
System.out.println("Session is idle");
});

// Later, to unsubscribe:
unsubscribe.close();

</details>

Step 4: add a custom tool

Now for the powerful part. Let's give Copilot the ability to call your code by defining a custom tool. We'll create a simple weather lookup tool.

<details open>
<summary><strong>Node.js / TypeScript</strong></summary>

Update index.ts:

typescript
import { CopilotClient, defineTool } from "@github/copilot-sdk";

// Define a tool that Copilot can call
const getWeather = defineTool("get_weather", {
description: "Get the current weather for a city",
parameters: {
type: "object",
properties: {
city: { type: "string", description: "The city name" },
},
required: ["city"],
},
handler: async (args: { city: string }) => {
const { city } = args;
// In a real app, you'd call a weather API here
const conditions = ["sunny", "cloudy", "rainy", "partly cloudy"];
const temp = Math.floor(Math.random() * 30) + 50;
const condition = conditions[Math.floor(Math.random() * conditions.length)];
return { city, temperature: ${temp}°F, condition };
},
});

const client = new CopilotClient();
const session = await client.createSession({
model: "auto",
streaming: true,
tools: [getWeather],
});

session.on("assistant.message_delta", (event) => {
process.stdout.write(event.data.deltaContent);
});

session.on("session.idle", () => {
console.log(); // New line when done
});

await session.sendAndWait({
prompt: "What's the weather like in Seattle and Tokyo?",
});

await client.stop();
process.exit(0);

</details>

<details>
<summary><strong>Python</strong></summary>

Update main.py:

text
/ Detailed source-code truncated for AI context efficiency. /

</details>

<details>
<summary><strong>Go</strong></summary>

Update main.go:

text
/ Detailed source-code truncated for AI context efficiency. /

</details>

<details>
<summary><strong>Rust</strong></summary>

Update src/main.rs:

text
/ Detailed source-code truncated for AI context efficiency. /

</details>

<details>
<summary><strong>.NET</strong></summary>

Update Program.cs:

csharp
using GitHub.Copilot;
using Microsoft.Extensions.AI;
using System.ComponentModel;

await using var client = new CopilotClient();

// Define a tool that Copilot can call
var getWeather = CopilotTool.DefineTool(
([Description("The city name")] string city) =>
{
// In a real app, you'd call a weather API here
var conditions = new[] { "sunny", "cloudy", "rainy", "partly cloudy" };
var temp = Random.Shared.Next(50, 80);
var condition = conditions[Random.Shared.Next(conditions.Length)];
return new { city, temperature = $"{temp}°F", condition };
},
factoryOptions: new AIFunctionFactoryOptions
{
Name = "get_weather",
Description = "Get the current weather for a city",
}
);

await using var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "auto",
OnPermissionRequest = PermissionHandler.ApproveAll,
Streaming = true,
Tools = [getWeather],
});

session.On<SessionEvent>(ev =>
{
if (ev is AssistantMessageDeltaEvent deltaEvent)
{
Console.Write(deltaEvent.Data.DeltaContent);
}
if (ev is SessionIdleEvent)
{
Console.WriteLine();
}
});

await session.SendAndWaitAsync(new MessageOptions
{
Prompt = "What's the weather like in Seattle and Tokyo?",
});

</details>

<details>
<summary><strong>Java</strong></summary>

Update HelloCopilot.java:


text
/ Detailed source-code truncated for AI context efficiency. /

</details>

Run it and you'll see Copilot call your tool to get weather data, then respond with the results!

Step 5: build an interactive assistant

Let's put it all together into a useful interactive assistant:

<details open>
<summary><strong>Node.js / TypeScript</strong></summary>

text
/ Detailed source-code truncated for AI context efficiency. /

Run with:

bash
npx tsx weather-assistant.ts

</details>

<details>
<summary><strong>Python</strong></summary>

Create weather_assistant.py:

text
/ Detailed source-code truncated for AI context efficiency. /

Run with:

bash
python weather_assistant.py

</details>

<details>
<summary><strong>Go</strong></summary>

Create weather-assistant.go:

text
/ Detailed source-code truncated for AI context efficiency. /

Run with:

bash
go run weather-assistant.go

</details>

<details>
<summary><strong>Rust</strong></summary>

Create src/main.rs:

text
/ Detailed source-code truncated for AI context efficiency. /

Run with:

bash
cargo run

</details>

<details>
<summary><strong>.NET</strong></summary>

Create a new console project and update Program.cs:

text
/ Detailed source-code truncated for AI context efficiency. /

Run with:

bash
dotnet run

</details>

<details>
<summary><strong>Java</strong></summary>

Create WeatherAssistant.java:


text
/ Detailed source-code truncated for AI context efficiency. /

Run with:

bash
javac -cp copilot-sdk.jar WeatherAssistant.java && java -cp .:copilot-sdk.jar WeatherAssistant

</details>

Example session:

text
🌤️  Weather Assistant (type 'exit' to quit)
Try: 'What's the weather in Paris?' or 'Compare weather in NYC and LA'

You: What's the weather in Seattle?
Assistant: Let me check the weather for Seattle...
It's currently 62°F and cloudy in Seattle.

You: How about Tokyo and London?
Assistant: I'll check both cities for you:
- Tokyo: 75°F and sunny
- London: 58°F and rainy

You: exit

You've built an assistant with a custom tool that Copilot can call!

How tools work

When you define a tool, you're telling Copilot:
1. What the tool does (description)
1. What parameters it needs (schema)
1. What code to run (handler)

Copilot decides when to call your tool based on the user's question. When it does:
1. Copilot sends a tool call request with the parameters
1. The SDK runs your handler function
1. The result is sent back to Copilot
1. Copilot incorporates the result into its response

What's next?

Now that you've got the basics, here are more powerful features to explore:

Connect to MCP servers

MCP (Model Context Protocol) servers provide pre-built tools. Connect to GitHub's MCP server to give Copilot access to repositories, issues, and pull requests:

typescript
const session = await client.createSession({
mcpServers: {
github: {
type: "http",
url: "https://api.githubcopilot.com/mcp/",
},
},
});

📖 Full MCP documentation → - Learn about local vs remote servers, all configuration options, and troubleshooting.

Create custom agents

Define specialized AI personas for specific tasks:

typescript
const session = await client.createSession({
customAgents: [{
name: "pr-reviewer",
displayName: "PR Reviewer",
description: "Reviews pull requests for best practices",
prompt: "You are an expert code reviewer. Focus on security, performance, and maintainability.",
}],
});

TIP

You can also set agent: "pr-reviewer" in the session config to pre-select this agent from the start. See the Custom Agents guide for details.

Customize the system message

Control the AI's behavior and personality by appending instructions:

typescript
const session = await client.createSession({
systemMessage: {
content: "You are a helpful assistant for our engineering team. Always be concise.",
},
});

For more fine-grained control, use mode: "customize" to override individual sections of the system prompt while preserving the rest:

typescript
const session = await client.createSession({
systemMessage: {
mode: "customize",
sections: {
tone: { action: "replace", content: "Respond in a warm, professional tone. Be thorough in explanations." },
code_change_rules: { action: "remove" },
guidelines: { action: "append", content: "\n* Always cite data sources" },
},
content: "Focus on financial analysis and reporting.",
},
});

Available section IDs: preamble, identity, tone, tool_efficiency, environment_context, code_change_rules, guidelines, safety, tool_instructions, custom_instructions, runtime_instructions, last_instructions.

identity and tool_instructions are section groups: they target a collection of related sub-sections as a unit. Use preamble to target just the identity preamble without affecting its sibling sub-sections.

Each override supports five actions: replace, remove, append, prepend, and preserve. The preserve action is a no-op that opts an individually-addressable section out of a group-level remove (for example, keep tone when removing the identity group). Unknown section IDs are handled gracefully: content from replace/append/prepend overrides is appended to additional instructions, and remove overrides are silently ignored.

See the language-specific SDK READMEs for examples in TypeScript, Python, Go, Rust, Java, and C#.

Connecting to an external CLI server

By default, the SDK automatically manages the Copilot CLI process lifecycle, starting and stopping the CLI as needed. However, you can also run the CLI in server mode separately and have the SDK connect to it. This can be useful for:

* Debugging: Keep the CLI running between SDK restarts to inspect logs
* Resource sharing: Multiple SDK clients can connect to the same CLI server
* Development: Run the CLI with custom settings or in a different environment

Running the CLI in server mode

Start the CLI in server mode using the --headless flag and optionally specify a port:

bash
copilot --headless --port 4321

If you don't specify a port, the CLI will choose a random available port.

By default the headless server only accepts connections from loopback (127.0.0.1), so the SDK must run on the same machine. To accept connections from other hosts (for example when running the CLI in a container or on a separate server), bind to a non-loopback address with --host:

bash

Listen on all interfaces


copilot --headless --host 0.0.0.0 --port 4321

WARNING

Exposing the headless server on a non-loopback address makes it reachable by anyone who can route to that address. Pair it with network controls (firewall, private network, reverse proxy) and authentication appropriate for your environment.

Connecting the SDK to the external server

Once the CLI is running in server mode, configure your SDK client to connect to it using the "cli url" option:

<details open>
<summary><strong>Node.js / TypeScript</strong></summary>

typescript
import { CopilotClient, approveAll } from "@github/copilot-sdk";

const client = new CopilotClient({
cliUrl: "localhost:4321"
});

// Use the client normally
const session = await client.createSession({ onPermissionRequest: approveAll });
// ...

</details>

<details>
<summary><strong>Python</strong></summary>

python
from copilot import CopilotClient, RuntimeConnection
from copilot.session import PermissionHandler

client = CopilotClient(connection=RuntimeConnection.for_uri("localhost:4321"))
await client.start()

Use the client normally


session = await client.create_session(on_permission_request=PermissionHandler.approve_all)

...

</details>

<details>
<summary><strong>Go</strong></summary>


go
package main

import (
"context"
"log"

copilot "github.com/github/copilot-sdk/go"
)

func main() {
ctx := context.Background()

client := copilot.NewClient(&copilot.ClientOptions{
Connection: copilot.URIConnection{URL: "localhost:4321"},
})

if err := client.Start(ctx); err != nil {
log.Fatal(err)
}
defer client.Stop()

// Use the client normally
_, _ = client.CreateSession(ctx, &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
})
}


go
import copilot "github.com/github/copilot-sdk/go"

client := copilot.NewClient(&copilot.ClientOptions{
Connection: copilot.URIConnection{URL: "localhost:4321"},
})

if err := client.Start(ctx); err != nil {
log.Fatal(err)
}
defer client.Stop()

// Use the client normally
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
})
// ...

</details>

<details>
<summary><strong>Rust</strong></summary>

rust
use std::sync::Arc;

use github_copilot_sdk::handler::ApproveAllHandler;
use github_copilot_sdk::{Client, ClientOptions, SessionConfig, Transport};

let mut options = ClientOptions::default();
options.transport = Transport::External {
host: "localhost".to_string(),
port: 4321,
connection_token: None,
};
let client = Client::start(options).await?;

// Use the client normally
let session = client
.create_session(SessionConfig::default().with_permission_handler(Arc::new(ApproveAllHandler)))
.await?;
// ...

</details>

<details>
<summary><strong>.NET</strong></summary>

csharp
using GitHub.Copilot;

using var client = new CopilotClient(new CopilotClientOptions
{
Connection = RuntimeConnection.ForUri("localhost:4321"),
});

// Use the client normally
await using var session = await client.CreateSessionAsync(new()
{
OnPermissionRequest = PermissionHandler.ApproveAll
});
// ...

</details>

<details>
<summary><strong>Java</strong></summary>

java
import com.github.copilot.CopilotClient;
import com.github.copilot.rpc.*;

var client = new CopilotClient(
new CopilotClientOptions().setCliUrl("localhost:4321")
);
client.start().get();

// Use the client normally
var session = client.createSession(
new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();
// ...

</details>

Note: When cli_url / cliUrl / Go's URIConnection is provided, or Rust uses Transport::External, the SDK will not spawn or manage a CLI process - it will only connect to the existing server at the specified URL.

Telemetry and observability

The Copilot SDK supports OpenTelemetry for distributed tracing. Provide a telemetry configuration to the client to enable trace export from the CLI process and automatic W3C Trace Context propagation between the SDK and CLI.

Enabling telemetry

Pass a telemetry (or Telemetry) config when creating the client. This is the opt-in—no separate "enabled" flag is needed.

<details open>
<summary><strong>Node.js / TypeScript</strong></summary>


typescript
import { CopilotClient } from "@github/copilot-sdk";

const client = new CopilotClient({
telemetry: {
otlpEndpoint: "http://localhost:4318",
},
});

Optional peer dependency: @opentelemetry/api

</details>

<details>
<summary><strong>Python</strong></summary>


python
from copilot import CopilotClient, CopilotClientOptions

client = CopilotClient(CopilotClientOptions(
telemetry={
"otlp_endpoint": "http://localhost:4318",
},
))

Install with telemetry extras: pip install copilot-sdk[telemetry] (provides opentelemetry-api)

</details>

<details>
<summary><strong>Go</strong></summary>


go
client := copilot.NewClient(&copilot.ClientOptions{
Telemetry: &copilot.TelemetryConfig{
OTLPEndpoint: "http://localhost:4318",
},
})

Dependency: go.opentelemetry.io/otel

</details>

<details>
<summary><strong>Rust</strong></summary>


rust
use github_copilot_sdk::{Client, ClientOptions, OtelExporterType, TelemetryConfig};

let mut options = ClientOptions::default();
options.telemetry = Some(
TelemetryConfig::new()
.with_exporter_type(OtelExporterType::OtlpHttp)
.with_otlp_endpoint("http://localhost:4318"),
);
let client = Client::start(options).await?;

No extra dependencies—the SDK injects telemetry environment variables for the spawned CLI process.

</details>

<details>
<summary><strong>.NET</strong></summary>


csharp
var client = new CopilotClient(new CopilotClientOptions
{
Telemetry = new TelemetryConfig
{
OtlpEndpoint = "http://localhost:4318",
},
});

No extra dependencies—uses built-in System.Diagnostics.Activity.

</details>

<details>
<summary><strong>Java</strong></summary>


java
import com.github.copilot.CopilotClient;
import com.github.copilot.rpc.*;

var client = new CopilotClient(new CopilotClientOptions()
.setTelemetry(new TelemetryConfig()
.setOtlpEndpoint("http://localhost:4318")));

Dependency: io.opentelemetry:opentelemetry-api

</details>

TelemetryConfig options

| Option | Node.js | Python | Go | Rust | Java | .NET | Description |
|---|---|---|---|---|---|---|---|
| OTLP endpoint | otlpEndpoint | otlp_endpoint | OTLPEndpoint | otlp_endpoint | otlpEndpoint | OtlpEndpoint | OTLP HTTP endpoint URL |
| OTLP protocol | otlpProtocol | otlp_protocol | OTLPProtocol | otlp_protocol | otlpProtocol | OtlpProtocol | OTLP HTTP protocol for all signals: "http/json" or "http/protobuf" |
| File path | filePath | file_path | FilePath | file_path | filePath | FilePath | File path for JSON-lines trace output |
| Exporter type | exporterType | exporter_type | ExporterType | exporter_type | exporterType | ExporterType | "otlp-http" or "file" |
| Source name | sourceName | source_name | SourceName | source_name | sourceName | SourceName | Instrumentation scope name |
| Capture content | captureContent | capture_content | CaptureContent | capture_content | captureContent | CaptureContent | Whether to capture message content |

The OTLP protocol field configures the CLI's "otlp-http" exporter for all signals. Leave it unset to use the CLI default, or set it to "http/protobuf" to export protobuf over HTTP.

File export

To write traces to a local file instead of an OTLP endpoint:


typescript
const client = new CopilotClient({
telemetry: {
filePath: "./traces.jsonl",
exporterType: "file",
},
});

Trace context propagation

Trace context is propagated automatically—no manual instrumentation is needed:

* SDK → CLI: traceparent and tracestate headers from the current span/activity are included in session.create, session.resume, and session.send RPC calls.
* CLI → SDK: When the CLI invokes tool handlers, the trace context from the CLI's span is propagated so your tool code runs under the correct parent span.

📖 OpenTelemetry Instrumentation Guide →—TelemetryConfig options, trace context propagation, and per-language dependencies.

Learn more

* Authentication Guide - GitHub OAuth, environment variables, and BYOK
* BYOK (Bring Your Own Key) - Use your own API keys from Microsoft Foundry, OpenAI, etc.
* Node.js SDK Reference
* Python SDK Reference
* Go SDK Reference
* Rust SDK Reference
* .NET SDK Reference
* Java SDK Reference
* Using MCP Servers - Integrate external tools via Model Context Protocol
* GitHub MCP Server Documentation
* MCP Servers Directory - Explore more MCP servers
* OpenTelemetry Instrumentation - TelemetryConfig, trace context propagation, and per-language dependencies

You did it! You've learned the core concepts of the GitHub Copilot SDK:
* ✅ Creating a client and session
* ✅ Sending messages and receiving responses
* ✅ Streaming for real-time output
* ✅ Defining custom tools that Copilot can call

Now go build something amazing! 🚀

Chapter: README (docs/README.md)

Copilot SDK

Welcome to the GitHub Copilot SDK docs. Whether you're building your first Copilot-powered app or deploying to production, you'll find what you need here.

Where to start

| I want to... | Go to |
|---|---|
| Build my first app | Getting Started—end-to-end tutorial with streaming & custom tools |
| Set up for production | Setup Guides—architecture, deployment patterns, scaling |
| Configure authentication | Authentication—GitHub OAuth, server-to-server authentication, environment variables, BYOK |
| Add features to my app | Features—hooks, custom agents, MCP, skills, and more |
| Debug an issue | Troubleshooting—common problems and solutions |

Documentation map

Getting Started

Step-by-step tutorial that takes you from zero to a working Copilot app with streaming responses and custom tools.

Setup

How to configure and deploy the SDK for your use case.

* Default Setup (Bundled CLI): the SDK includes the CLI automatically
* Local CLI: use your own CLI binary or running instance
* Backend Services: server-side with headless CLI over TCP
* GitHub OAuth: implement the OAuth flow
* Azure Managed Identity: BYOK with Microsoft Foundry
* Scaling & Multi-Tenancy: horizontal scaling, isolation patterns
* Multi-Tenancy & Server Deployments: mode: "empty", session isolation, integration IDs, sessionFs

Authentication

Configuring how users and services authenticate with Copilot.

* Authentication Overview: methods, priority order, and examples
* Server-to-server authentication: use GitHub Actions or GitHub App installation tokens for organization-attributed automation
* Bring Your Own Key (BYOK): use your own API keys from OpenAI, Azure, Anthropic, and more

Features

Guides for building with the SDK's capabilities.

* Hooks: intercept and customize session behavior
* Custom Agents: define specialized sub-agents
* MCP Servers: integrate Model Context Protocol servers
* Skills: load reusable prompt modules
* Plugin Directories: bundle skills, hooks, MCP servers, and agents as a single loadable plugin
* Session limits: set an AI Credits budget for a session
* Image Input: send images as attachments
* Streaming Events: real-time event reference
* Steering & Queueing: message delivery modes
* Session Persistence: resume sessions across restarts
* Remote Sessions: share sessions to GitHub web and mobile via Mission Control
* Cloud Sessions: run sessions on GitHub-hosted compute with the cloud: option
* Fleet Mode: dispatch parallel sub-agents for parallelizable work

Hooks Reference

Detailed API reference for each session hook.

* Pre-Tool Use: approve, deny, or modify tool calls
* Post-Tool Use: transform tool results
* User Prompt Submitted: modify or filter user messages
* User Prompt Transformed: inspect or replace model-facing prompts
* Session Lifecycle: session start and end
* Error Handling: custom error handling

Troubleshooting

* Debugging Guide: common issues and solutions
* MCP Debugging: MCP-specific troubleshooting
* Compatibility: SDK vs CLI feature matrix

Observability

* OpenTelemetry Instrumentation: built-in TelemetryConfig and trace context propagation

Integrations

Guides for using the SDK with other platforms and frameworks.

* Microsoft Agent Framework: MAF multi-agent workflows