Skip to content

Latest commit

 

History

History
 
 

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

README.md

Chapter 08: The Copilot SDK

Go beyond the CLI — embed Copilot intelligence directly into your own applications.

In this chapter, you'll learn how to use the GitHub Copilot SDK to build custom applications powered by Copilot. Instead of interacting with Copilot through the terminal, you'll write code that talks to Copilot programmatically — sending messages, streaming responses, and defining custom tools that Copilot can call.

💡 Note: The Copilot SDK is a separate tool from the Copilot CLI you've used in previous chapters. The CLI is for terminal-based workflows; the SDK is for embedding Copilot capabilities into your own apps, scripts, and services.

🎯 Learning Objectives

By the end of this chapter, you'll be able to:

  • Install and configure the Copilot SDK for TypeScript/Node.js and Python
  • Send messages and receive responses from Copilot programmatically
  • Stream responses in real-time for interactive experiences
  • Define custom tools that Copilot can invoke
  • Build an interactive assistant with tool integration

⏱️ Estimated Time: ~90 minutes (20 min reading + 70 min hands-on labs)


🧩 Real-World Analogy: The Power Grid

Think of the Copilot CLI as plugging a lamp into a wall outlet — it works great, it's easy, and you get light immediately.

The Copilot SDK is like connecting directly to the power grid. You can:

  • Build your own appliances (custom apps)
  • Control exactly how much power you use (streaming, tools)
  • Wire up complex systems (multi-tool assistants)
  • Create entirely new experiences (chatbots, automation, agents)

The CLI gives you a flashlight. The SDK lets you build a power plant.


🔑 Core Concepts

What is the Copilot SDK?

The Copilot SDK is a library (available for TypeScript, Python, Go, Rust, .NET, and Java) that lets your code communicate with GitHub Copilot. Under the hood, the SDK manages a Copilot CLI process and communicates with it via RPC (Remote Procedure Call).

Key Components

Component What It Does
Client Starts and manages the connection to Copilot
Session A conversation context — like a chat thread
Messages Prompts you send and responses you receive
Streaming Receive response chunks in real-time (word by word)
Tools Functions you define that Copilot can call

How It Works

Your App  →  Copilot SDK  →  Copilot CLI (managed automatically)  →  GitHub Copilot
   ↑                                                                        ↓
   ←──────────────── responses / tool calls / streaming ←───────────────────┘
  1. Your app creates a Client (which starts the CLI behind the scenes)
  2. You open a Session (a conversation)
  3. You send a message (a prompt)
  4. Copilot responds — either all at once or streamed word by word
  5. If you defined tools, Copilot can call them and incorporate the results

✅ Prerequisites

Before starting the labs, make sure you have:

  • GitHub Copilot CLI installed and authenticated (from Chapter 00)
  • Node.js 20+ (for the TypeScript lab)
  • Python 3.11+ (for the Python lab)

Verify:

copilot --version
node --version    # Should be 20+
python3 --version # Should be 3.11+

🧪 Lab 1: TypeScript/Node.js

Build a weather assistant using the Copilot SDK with TypeScript.


Step 1: Set Up the Project

mkdir copilot-ts-lab && cd copilot-ts-lab
npm init -y --init-type module
npm install @github/copilot-sdk tsx

💡 What's tsx? It's a TypeScript runner that lets you execute .ts files directly without a separate compile step. Think of it like ts-node but faster.


Step 2: Send Your First Message

Create index.ts:

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:

npx tsx index.ts

You should see:

4

That's it — five lines of code and you've got Copilot responding to your app! The SDK handles starting the CLI, authenticating, and managing the session for you.


Step 3: Add Streaming Responses

Waiting for the full response before displaying anything isn't great for user experience. Let's stream the response word by word.

Update index.ts:

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

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

// Listen for response chunks as they arrive
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: "Explain what the GitHub Copilot SDK does." });

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

Run it again:

npx tsx index.ts

Now you'll see the response appear word by word, just like a real chat experience.


Step 4: Add a Custom Tool

Here's where it gets powerful. You can define tools — functions in your code that Copilot can decide to call based on the user's question.

Let's create a weather lookup tool:

Update index.ts:

import { CopilotClient, defineTool, approveAll } 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() * 3) + 20;
        const condition = conditions[Math.floor(Math.random() * conditions.length)];
        return { city, temperature: `${temp}°C`, condition };
    },
});

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

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

session.on("session.idle", () => {
    console.log();
});

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

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

Run it:

npx tsx index.ts

Copilot will call your get_weather function for each city, then incorporate the results into a natural language response. You didn't write any prompt engineering — Copilot figured out it needs weather data and called your tool automatically.


Step 5: Build an Interactive Assistant

Let's put it all together into a REPL (Read-Eval-Print Loop) where you can chat with Copilot:

Create weather-assistant.ts:

import { CopilotClient, defineTool, approveAll } from "@github/copilot-sdk";
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() * 3) + 20;
        const condition = conditions[Math.floor(Math.random() * conditions.length)];
        return { city, temperature: `${temp}°C`, condition };
    },
});

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

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 Berlin?'\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 it:

npx tsx weather-assistant.ts

Example session:

🌤️  Weather Assistant (type 'exit' to quit)
   Try: 'What's the weather in Berlin?'

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 Munich and London?
Assistant: I'll check both cities for you:
- Tokyo: 75°F and sunny
- London: 58°F and rainy

You: exit

🧪 Lab 2: Python

Build the same weather assistant using the Copilot SDK with Python.


Step 1: Set Up the Project

mkdir copilot-python-lab && cd copilot-python-lab
python3 -m venv .venv
source .venv/bin/activate
pip install github-copilot-sdk

💡 Why a virtual environment? It keeps your project dependencies isolated. The github-copilot-sdk package won't conflict with anything else on your system.


Step 2: Send Your First Message

Create main.py:

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:

python main.py

You should see:

4

💡 Why asyncio? The Copilot SDK uses async I/O to communicate with the CLI process. The async/await pattern lets your app do other work while waiting for Copilot's response.


Step 3: Add Streaming Responses

Update main.py to stream responses as they arrive:

import asyncio
import sys
from copilot import CopilotClient
from copilot.session import PermissionHandler
from copilot.generated.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("Explain what the GitHub Copilot SDK does.")

    await client.stop()

asyncio.run(main())

Run it:

python main.py

The response streams in real-time — much better for interactive experiences.


Step 4: Add a Custom Tool

Define a weather tool that Copilot can call. Python uses Pydantic models to describe tool parameters:

Update main.py:

import asyncio
import random
import sys
from copilot import CopilotClient
from copilot.session import PermissionHandler
from copilot.tools import define_tool
from copilot.generated.session_events import SessionEventType
from pydantic import BaseModel, Field

# Define the parameters using a Pydantic model
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(20, 40)
    condition = random.choice(conditions)
    return {"city": city, "temperature": f"{temp}°C", "condition": condition}

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

    session = await client.create_session(
        on_permission_request=PermissionHandler.approve_all,
        model="auto",
        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("What's the weather like in Munich and Bonn?")

    await client.stop()

asyncio.run(main())

Run it:

python main.py

Copilot calls your get_weather function for each city, then weaves the results into a natural response.


Step 5: Build an Interactive Assistant

Create weather_assistant.py:

import asyncio
import random
import sys
from copilot import CopilotClient
from copilot.session import PermissionHandler
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(20, 40)
    condition = random.choice(conditions)
    return {"city": city, "temperature": f"{temp}°C", "condition": condition}

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

    session = await client.create_session(
        on_permission_request=PermissionHandler.approve_all,
        model="auto",
        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 Berlin?' or 'Compare weather in Munich and Merano'\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(user_input)
        print("\n")

    await client.stop()

asyncio.run(main())

Run it:

python weather_assistant.py

Example session:

🌤️  Weather Assistant (type 'exit' to quit)
   Try: 'What's the weather in Berlin?' or 'Compare weather in Munich and Merano'

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

🧠 How Tools Work

When you define a tool, you're telling Copilot three things:

  1. What the tool does — the description (e.g., "Get the current weather for a city")
  2. What parameters it needs — the schema (e.g., city: string)
  3. What code to run — the handler function

Here's the flow when Copilot uses a tool:

User: "What's the weather in Munich?"
         │
         ▼
Copilot sees the question matches the tool description
         │
         ▼
Copilot sends a tool call: get_weather({ city: "Munich" })
         │
         ▼
SDK runs YOUR handler function
         │
         ▼
Result returned: { city: "Munich", temperature: "32°C", condition: "sunny" }
         │
         ▼
Copilot incorporates result into natural language response
         │
         ▼
User sees: "It's currently 32°C and sunny in Munich!"

💡 Key insight: You don't need to write prompt engineering to get Copilot to use your tools. Just describe what the tool does clearly, and Copilot figures out when to use it.


🚀 Going Further

Once you're comfortable with the basics, explore these advanced features:

Connect to MCP Servers

Give your app access to external tools via MCP (Model Context Protocol):

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

Create Custom Agents

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.",
    }],
});

Customize the System Message

Control Copilot's behavior and personality:

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

Connect to an External CLI Server

For debugging or resource sharing, run the CLI separately:

# Start CLI in server mode
copilot --headless --port 4321

Then connect your SDK client to it:

const client = new CopilotClient({
    cliUrl: "localhost:4321"
});
from copilot import CopilotClient, RuntimeConnection

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

Practice

Put everything you've learned into practice.


▶️ Try It Yourself

  1. Extend the weather assistant: Add a second tool (e.g., get_time that returns the current time in a timezone). Ask Copilot "What's the weather and time in Tokyo?" and watch it call both tools.

  2. Build a book assistant: Create a tool that searches your book collection (use samples/book-app-project/data.json as the data source). Ask Copilot questions like "Do I have any books by Orwell?" or "What books were published before 1960?"

  3. Add system messages: Try adding a systemMessage to your session that changes Copilot's personality. Make it respond like a pirate, or a formal butler, or a sports commentator.


📝 Assignment

Main Challenge: Book Collection Assistant

Build a Copilot-powered assistant for the book collection app using either TypeScript or Python:

  1. Define two tools:

    • search_books — searches books by title or author (reads from samples/book-app-project/data.json)
    • add_book — adds a new book to the collection
  2. Enable streaming so responses appear in real-time

  3. Create an interactive loop where the user can chat with the assistant

  4. Test these interactions:

    • "Do I have any science fiction books?"
    • "Add a new book: 'The Martian' by Andy Weir, published 2011"
    • "List all books by their publication year"

Success criteria: Your assistant correctly uses the tools when relevant and responds naturally when no tool is needed.

💡 Hints (click to expand)

For the search_books tool:

  • Read data.json using fs.readFileSync (TypeScript) or json.load (Python)
  • Filter books where the title or author matches the search query (case-insensitive)
  • Return the matching books as an array

For the add_book tool:

  • Read the current data, append the new book, write back to the file
  • Return a confirmation message

Tool parameters:

  • search_books: { query: string } — the search term
  • add_book: { title: string, author: string, year: number } — book details

Remember: You define WHAT the tool does. Copilot decides WHEN to use it.


🔧 Common Mistakes (click to expand)
Mistake What Happens Fix
Forgetting await client.stop() CLI process keeps running in the background Always call stop() before exiting
Not enabling streaming: true message_delta events never fire Set streaming: true in session config
Tool handler returns wrong type Copilot gets confused or errors Return a plain object/dict, not a string
Missing process.exit(0) (TypeScript) Script hangs after completion Add explicit exit at the end
Not using sys.stdout.flush() (Python) Streamed text appears all at once Flush after each write
Forgetting await client.start() (Python) Session creation fails Always start the client before creating sessions

Summary

🔑 Key Takeaways

  1. The SDK embeds Copilot in your apps: Go beyond the terminal — build custom tools, assistants, and automation
  2. 5 lines to start: Create a client, open a session, send a message — that's the minimum
  3. Streaming makes it interactive: Real-time responses feel natural and responsive
  4. Tools are the superpower: Define what your code can do, and Copilot decides when to call it
  5. Same patterns, any language: The SDK works the same way across TypeScript, Python, Go, Rust, .NET, and Java

📚 Resources


← Back to Chapter 07 | Return to Course Home →