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.
The Copilot SDK Supercharged supports 21 languages. This guide covers the core SDKs in detail, with links to all language SDKs.
What you'll build:
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!
Before you begin, make sure you have:
Verify the CLI is working:
copilot --version
First, create a new directory and initialize your project:
mkdir copilot-demo && cd copilot-demo
npm init -y --init-type module
Then install the SDK and TypeScript runner:
npm install copilot-sdk-supercharged tsx
pip install github-copilot-sdk
First, create a new directory and initialize your module:
mkdir copilot-demo && cd copilot-demo
go mod init copilot-demo
Then install the SDK:
go get github.com/github/copilot-sdk/go
First, create a new console project:
dotnet new console -n CopilotDemo && cd CopilotDemo
Then add the SDK:
dotnet add package GitHub.Copilot.SDK
Add the dependency to your pom.xml:
<dependency>
<groupId>com.github.copilot.supercharged</groupId>
<artifactId>copilot-sdk-supercharged</artifactId>
<version>0.1.0</version>
</dependency>
Or clone the SDK directory from this repo into your project.
Add to your Cargo.toml:
[dependencies]
copilot-sdk-supercharged = { path = "../rust" }
Or reference the SDK directory from this repo.
gem install copilot-sdk-supercharged
Or add to your Gemfile:
gem 'copilot-sdk-supercharged', path: '../ruby'
composer require github/copilot-sdk-supercharged
Add to your Package.swift:
.package(path: "../swift")
Add the SDK as a local dependency in build.gradle.kts:
implementation(project(":copilot-sdk-supercharged-kotlin"))
See the individual SDK READMEs for installation instructions:
| Language | README |
|---|---|
| C++ | cpp/README.md |
| C | c/README.md |
| Dart | dart/README.md |
| Scala | scala/README.md |
| R | r/README.md |
| Perl | perl/README.md |
| Lua | lua/README.md |
| Shell/Bash | shell/README.md |
| Elixir | elixir/README.md |
| Haskell | haskell/README.md |
| Clojure | clojure/README.md |
Create a new file and add the following code. This is the simplest way to use the SDK—about 5 lines of code.
Create index.ts:
import { CopilotClient } from "copilot-sdk-supercharged";
const client = new CopilotClient();
const session = await client.createSession({ model: "gpt-4.1" });
const response = await session.sendAndWait({ prompt: "What is 2 + 2?" });
console.log(response?.data.content);
await client.stop();
process.exit(0);
Run it:
npx tsx index.ts
Create main.py:
import asyncio
from copilot import CopilotClient
async def main():
client = CopilotClient()
await client.start()
session = await client.create_session({"model": "gpt-4.1"})
response = await session.send_and_wait({"prompt": "What is 2 + 2?"})
print(response.data.content)
await client.stop()
asyncio.run(main())
Run it:
python main.py
Create main.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: "gpt-4.1"})
if err != nil {
log.Fatal(err)
}
response, err := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "What is 2 + 2?"})
if err != nil {
log.Fatal(err)
}
fmt.Println(*response.Data.Content)
os.Exit(0)
}
Run it:
go run main.go
Create a new console project and add this to Program.cs:
using GitHub.Copilot.SDK;
await using var client = new CopilotClient();
await using var session = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-4.1" });
var response = await session.SendAndWaitAsync(new MessageOptions { Prompt = "What is 2 + 2?" });
Console.WriteLine(response?.Data.Content);
Run it:
dotnet run
You should see:
4
Congratulations! You just built your first Copilot-powered app.
Right now, you wait for the complete response before seeing anything. Let's make it interactive by streaming the response as it's generated.
Update index.ts:
import { CopilotClient } from "copilot-sdk-supercharged";
const client = new CopilotClient();
const session = await client.createSession({
model: "gpt-4.1",
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);
Update main.py:
import asyncio
import sys
from copilot import CopilotClient
from copilot.generated.session_events import SessionEventType
async def main():
client = CopilotClient()
await client.start()
session = await client.create_session({
"model": "gpt-4.1",
"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({"prompt": "Tell me a short joke"})
await client.stop()
asyncio.run(main())
Update main.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: "gpt-4.1",
Streaming: true,
})
if err != nil {
log.Fatal(err)
}
// Listen for response chunks
session.On(func(event copilot.SessionEvent) {
if event.Type == "assistant.message_delta" {
fmt.Print(*event.Data.DeltaContent)
}
if event.Type == "session.idle" {
fmt.Println()
}
})
_, err = session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Tell me a short joke"})
if err != nil {
log.Fatal(err)
}
os.Exit(0)
}
Update Program.cs:
using GitHub.Copilot.SDK;
await using var client = new CopilotClient();
await using var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-4.1",
Streaming = true,
});
// Listen for response chunks
session.On(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" });
Run the code again. You'll see the response appear word by word.
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 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();
# 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()
// 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) {
if event.Type == "session.idle" {
fmt.Println("Session is idle")
} else if event.Type == "assistant.message" {
fmt.Println("Message:", *event.Data.Content)
}
})
// Later, to unsubscribe:
unsubscribe()
// Subscribe to all events
var unsubscribe = session.On(ev => Console.WriteLine($"Event: {ev.Type}"));
// Filter by event type using pattern matching
session.On(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();
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.
Update index.ts:
import { CopilotClient, defineTool } from "copilot-sdk-supercharged";
// 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: "gpt-4.1",
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);
Update main.py:
import asyncio
import random
import sys
from copilot import CopilotClient
from copilot.tools import define_tool
from copilot.generated.session_events import SessionEventType
from pydantic import BaseModel, Field
# Define the parameters for the tool using Pydantic
class GetWeatherParams(BaseModel):
city: str = Field(description="The name of the city to get weather for")
# Define a tool that Copilot can call
@define_tool(description="Get the current weather for a city")
async def get_weather(params: GetWeatherParams) -> dict:
city = params.city
# In a real app, you'd call a weather API here
conditions = ["sunny", "cloudy", "rainy", "partly cloudy"]
temp = random.randint(50, 80)
condition = random.choice(conditions)
return {"city": city, "temperature": f"{temp}°F", "condition": condition}
async def main():
client = CopilotClient()
await client.start()
session = await client.create_session({
"model": "gpt-4.1",
"streaming": True,
"tools": [get_weather],
})
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()
session.on(handle_event)
await session.send_and_wait({
"prompt": "What's the weather like in Seattle and Tokyo?"
})
await client.stop()
asyncio.run(main())
Update main.go:
package main
import (
"context"
"fmt"
"log"
"math/rand"
"os"
copilot "github.com/github/copilot-sdk/go"
)
// Define the parameter type
type WeatherParams struct {
City string `json:"city" jsonschema:"The city name"`
}
// Define the return type
type WeatherResult struct {
City string `json:"city"`
Temperature string `json:"temperature"`
Condition string `json:"condition"`
}
func main() {
ctx := context.Background()
// Define a tool that Copilot can call
getWeather := copilot.DefineTool(
"get_weather",
"Get the current weather for a city",
func(params WeatherParams, inv copilot.ToolInvocation) (WeatherResult, error) {
// In a real app, you'd call a weather API here
conditions := []string{"sunny", "cloudy", "rainy", "partly cloudy"}
temp := rand.Intn(30) + 50
condition := conditions[rand.Intn(len(conditions))]
return WeatherResult{
City: params.City,
Temperature: fmt.Sprintf("%d°F", temp),
Condition: condition,
}, nil
},
)
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: "gpt-4.1",
Streaming: true,
Tools: []copilot.Tool{getWeather},
})
if err != nil {
log.Fatal(err)
}
session.On(func(event copilot.SessionEvent) {
if event.Type == "assistant.message_delta" {
fmt.Print(*event.Data.DeltaContent)
}
if event.Type == "session.idle" {
fmt.Println()
}
})
_, err = session.SendAndWait(ctx, copilot.MessageOptions{
Prompt: "What's the weather like in Seattle and Tokyo?",
})
if err != nil {
log.Fatal(err)
}
os.Exit(0)
}
Update Program.cs:
using GitHub.Copilot.SDK;
using Microsoft.Extensions.AI;
using System.ComponentModel;
await using var client = new CopilotClient();
// Define a tool that Copilot can call
var getWeather = AIFunctionFactory.Create(
([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 };
},
"get_weather",
"Get the current weather for a city"
);
await using var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-4.1",
Streaming = true,
Tools = [getWeather],
});
session.On(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?",
});
Run it and you'll see Copilot call your tool to get weather data, then respond with the results!
Let's put it all together into a useful interactive assistant:
import { CopilotClient, defineTool } from "copilot-sdk-supercharged";
import * as readline from "readline";
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 ({ city }) => {
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: "gpt-4.1",
streaming: true,
tools: [getWeather],
});
session.on("assistant.message_delta", (event) => {
process.stdout.write(event.data.deltaContent);
});
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
console.log("🌤️ Weather Assistant (type 'exit' to quit)");
console.log(" Try: 'What's the weather in Paris?'\n");
const prompt = () => {
rl.question("You: ", async (input) => {
if (input.toLowerCase() === "exit") {
await client.stop();
rl.close();
return;
}
process.stdout.write("Assistant: ");
await session.sendAndWait({ prompt: input });
console.log("\n");
prompt();
});
};
prompt();
Run with:
npx tsx weather-assistant.ts
Create weather_assistant.py:
import asyncio
import random
import sys
from copilot import CopilotClient
from copilot.tools import define_tool
from copilot.generated.session_events import SessionEventType
from pydantic import BaseModel, Field
class GetWeatherParams(BaseModel):
city: str = Field(description="The name of the city to get weather for")
@define_tool(description="Get the current weather for a city")
async def get_weather(params: GetWeatherParams) -> dict:
city = params.city
conditions = ["sunny", "cloudy", "rainy", "partly cloudy"]
temp = random.randint(50, 80)
condition = random.choice(conditions)
return {"city": city, "temperature": f"{temp}°F", "condition": condition}
async def main():
client = CopilotClient()
await client.start()
session = await client.create_session({
"model": "gpt-4.1",
"streaming": True,
"tools": [get_weather],
})
def handle_event(event):
if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA:
sys.stdout.write(event.data.delta_content)
sys.stdout.flush()
session.on(handle_event)
print("🌤️ Weather Assistant (type 'exit' to quit)")
print(" Try: 'What's the weather in Paris?' or 'Compare weather in NYC and LA'\n")
while True:
try:
user_input = input("You: ")
except EOFError:
break
if user_input.lower() == "exit":
break
sys.stdout.write("Assistant: ")
await session.send_and_wait({"prompt": user_input})
print("\n")
await client.stop()
asyncio.run(main())
Run with:
python weather_assistant.py
Create weather-assistant.go:
package main
import (
"bufio"
"context"
"fmt"
"log"
"math/rand"
"os"
"strings"
copilot "github.com/github/copilot-sdk/go"
)
type WeatherParams struct {
City string `json:"city" jsonschema:"The city name"`
}
type WeatherResult struct {
City string `json:"city"`
Temperature string `json:"temperature"`
Condition string `json:"condition"`
}
func main() {
ctx := context.Background()
getWeather := copilot.DefineTool(
"get_weather",
"Get the current weather for a city",
func(params WeatherParams, inv copilot.ToolInvocation) (WeatherResult, error) {
conditions := []string{"sunny", "cloudy", "rainy", "partly cloudy"}
temp := rand.Intn(30) + 50
condition := conditions[rand.Intn(len(conditions))]
return WeatherResult{
City: params.City,
Temperature: fmt.Sprintf("%d°F", temp),
Condition: condition,
}, nil
},
)
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: "gpt-4.1",
Streaming: true,
Tools: []copilot.Tool{getWeather},
})
if err != nil {
log.Fatal(err)
}
session.On(func(event copilot.SessionEvent) {
if event.Type == "assistant.message_delta" {
if event.Data.DeltaContent != nil {
fmt.Print(*event.Data.DeltaContent)
}
}
if event.Type == "session.idle" {
fmt.Println()
}
})
fmt.Println("🌤️ Weather Assistant (type 'exit' to quit)")
fmt.Println(" Try: 'What's the weather in Paris?' or 'Compare weather in NYC and LA'\n")
scanner := bufio.NewScanner(os.Stdin)
for {
fmt.Print("You: ")
if !scanner.Scan() {
break
}
input := scanner.Text()
if strings.ToLower(input) == "exit" {
break
}
fmt.Print("Assistant: ")
_, err = session.SendAndWait(ctx, copilot.MessageOptions{Prompt: input})
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
break
}
fmt.Println()
}
if err := scanner.Err(); err != nil {
fmt.Fprintf(os.Stderr, "Input error: %v\n", err)
}
}
Run with:
go run weather-assistant.go
Create a new console project and update Program.cs:
using GitHub.Copilot.SDK;
using Microsoft.Extensions.AI;
using System.ComponentModel;
// Define the weather tool using AIFunctionFactory
var getWeather = AIFunctionFactory.Create(
([Description("The city name")] string city) =>
{
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 };
},
"get_weather",
"Get the current weather for a city");
await using var client = new CopilotClient();
await using var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-4.1",
Streaming = true,
Tools = [getWeather]
});
// Listen for response chunks
session.On(ev =>
{
if (ev is AssistantMessageDeltaEvent deltaEvent)
{
Console.Write(deltaEvent.Data.DeltaContent);
}
if (ev is SessionIdleEvent)
{
Console.WriteLine();
}
});
Console.WriteLine("🌤️ Weather Assistant (type 'exit' to quit)");
Console.WriteLine(" Try: 'What's the weather in Paris?' or 'Compare weather in NYC and LA'\n");
while (true)
{
Console.Write("You: ");
var input = Console.ReadLine();
if (string.IsNullOrEmpty(input) || input.Equals("exit", StringComparison.OrdinalIgnoreCase))
{
break;
}
Console.Write("Assistant: ");
await session.SendAndWaitAsync(new MessageOptions { Prompt = input });
Console.WriteLine("\n");
}
Run with:
dotnet run
Example session:
🌤️ 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!
When you define a tool, you're telling Copilot:
Copilot decides when to call your tool based on the user's question. When it does:
Now that you've got the basics, here are more powerful features to explore:
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:
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.
Define specialized AI personas for specific tasks:
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.",
}],
});
Control the AI's behavior and personality:
const session = await client.createSession({
systemMessage: {
content: "You are a helpful assistant for our engineering team. Always be concise.",
},
});
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:
Start the CLI in server mode using the --headless flag and optionally specify a port:
copilot --headless --port 4321
If you don't specify a port, the CLI will choose a random available port.
Once the CLI is running in server mode, configure your SDK client to connect to it using the "cli url" option:
import { CopilotClient } from "copilot-sdk-supercharged";
const client = new CopilotClient({
cliUrl: "localhost:4321"
});
// Use the client normally
const session = await client.createSession();
// ...
from copilot import CopilotClient
client = CopilotClient({
"cli_url": "localhost:4321"
})
await client.start()
# Use the client normally
session = await client.create_session()
# ...
import copilot "github.com/github/copilot-sdk/go"
client := copilot.NewClient(&copilot.ClientOptions{
CLIUrl: "localhost:4321",
})
if err := client.Start(ctx); err != nil {
log.Fatal(err)
}
defer client.Stop()
// Use the client normally
session, err := client.CreateSession(ctx, nil)
// ...
using GitHub.Copilot.SDK;
using var client = new CopilotClient(new CopilotClientOptions
{
CliUrl = "localhost:4321",
UseStdio = false
});
// Use the client normally
await using var session = await client.CreateSessionAsync();
// ...
Note: When cli_url / cliUrl / CLIUrl is provided, the SDK will not spawn or manage a CLI process - it will only connect to the existing server at the specified URL.
You did it! You've learned the core concepts of the GitHub Copilot SDK:
Now go build something amazing in any of our 20 supported languages!
Can you improve this documentation? These fine people already did:
Patrick Nikoletich, Jeremiah Isaacson, Adrien Friggeri, Copilot, Anass Kartit, iota, Vic Perdana, Aditya Nagesh, Burke Holland, Yohan Lasorsa & moonshade9Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |