{"meta":{"title":"Build your first Copilot-powered app","intro":"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.","product":"GitHub Copilot","breadcrumbs":[{"href":"/en/copilot","title":"GitHub Copilot"},{"href":"/en/copilot/how-tos","title":"How-tos"},{"href":"/en/copilot/how-tos/copilot-sdk","title":"Copilot SDK"},{"href":"/en/copilot/how-tos/copilot-sdk/getting-started","title":"Getting Started"}],"documentType":"article"},"body":"# Build your first Copilot-powered app\n\nIn 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.\n\n<!-- markdownlint-disable GHD046 GHD005 -->\n\n<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->\n\n**What you'll build:**\n\n```text\nYou: What's the weather like in Seattle?\nCopilot: Let me check the weather for Seattle...\n         Currently 62°F and cloudy with a chance of rain.\n         Typical Seattle weather!\n\nYou: How about Tokyo?\nCopilot: In Tokyo it's 75°F and sunny. Great day to be outside!\n```\n\n## Prerequisites\n\nBefore you begin, make sure you have:\n\n* **GitHub Copilot CLI** installed and authenticated (the Node.js, Python, and .NET SDKs provide the CLI automatically—see [Default setup (bundled CLI)](/en/copilot/how-tos/copilot-sdk/setup/bundled-cli). Required for Go, Java, and Rust unless using their application-level CLI bundling features.)\n* Your preferred language runtime:\n  * **Node.js** 20+ or **Python** 3.11+ or **Go** 1.24+ or **Rust** 1.94+ or **Java** 17+ or **.NET** 8.0+\n\nVerify the CLI is working:\n\n```bash\ncopilot --version\n```\n\n## Step 1: install the SDK\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\nFirst, create a new directory and initialize your project:\n\n```bash\nmkdir copilot-demo && cd copilot-demo\nnpm init -y --init-type module\n```\n\nThen install the SDK and TypeScript runner:\n\n```bash\nnpm install @github/copilot-sdk tsx\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n```bash\npip install github-copilot-sdk\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\nFirst, create a new directory and initialize your module:\n\n```bash\nmkdir copilot-demo && cd copilot-demo\ngo mod init copilot-demo\n```\n\nThen install the SDK:\n\n```bash\ngo get github.com/github/copilot-sdk/go\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"rust\" data-label=\"Rust\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Rust</div>\n\nFirst, create a new binary crate:\n\n```bash\ncargo new copilot-demo && cd copilot-demo\n```\n\nThen install the SDK and direct dependencies used by the examples:\n\n```bash\ncargo add github-copilot-sdk --features derive\n# Used by #[tokio::main] and tokio::spawn\ncargo add tokio --features rt-multi-thread,macros\n# Used by custom-tool parameter derives later in this guide\ncargo add serde --features derive\ncargo add schemars\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\nFirst, create a new console project:\n\n```bash\ndotnet new console -n CopilotDemo && cd CopilotDemo\n```\n\nThen add the SDK:\n\n```bash\ndotnet add package GitHub.Copilot.SDK\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\nFirst, create a new directory and initialize your project.\n\n**Maven**—add to your `pom.xml`:\n\n```xml\n<dependency>\n    <groupId>com.github</groupId>\n    <artifactId>copilot-sdk-java</artifactId>\n    <version>${copilot.sdk.version}</version>\n</dependency>\n```\n\n**Gradle**—add to your `build.gradle`:\n\n```groovy\nimplementation 'com.github:copilot-sdk-java:${copilotSdkVersion}'\n```\n\n</div>\n\n</div>\n\n## Step 2: send your first message\n\nCreate a new file and add the following code. This is the simplest way to use the SDK—about 5 lines of code.\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\nCreate `index.ts`:\n\n```typescript\nimport { CopilotClient } from \"@github/copilot-sdk\";\n\nconst client = new CopilotClient();\nconst session = await client.createSession({ model: \"auto\" });\n\nconst response = await session.sendAndWait({ prompt: \"What is 2 + 2?\" });\nconsole.log(response?.data.content);\n\nawait client.stop();\nprocess.exit(0);\n```\n\nRun it:\n\n```bash\nnpx tsx index.ts\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\nCreate `main.py`:\n\n```python\nimport asyncio\nfrom copilot import CopilotClient\nfrom copilot.session import PermissionHandler\n\nasync def main():\n    client = CopilotClient()\n    await client.start()\n\n    session = await client.create_session(on_permission_request=PermissionHandler.approve_all, model=\"auto\")\n    response = await session.send_and_wait(\"What is 2 + 2?\")\n    print(response.data.content)\n\n    await client.stop()\n\nasyncio.run(main())\n```\n\nRun it:\n\n```bash\npython main.py\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\nCreate `main.go`:\n\n```golang\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\tcopilot \"github.com/github/copilot-sdk/go\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\tclient := copilot.NewClient(nil)\n\tif err := client.Start(ctx); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer client.Stop()\n\n\tsession, err := client.CreateSession(ctx, &copilot.SessionConfig{Model: \"auto\"})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tresponse, err := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: \"What is 2 + 2?\"})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tif d, ok := response.Data.(*copilot.AssistantMessageData); ok {\n\t\tfmt.Println(d.Content)\n\t}\n\tos.Exit(0)\n}\n```\n\nRun it:\n\n```bash\ngo run main.go\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"rust\" data-label=\"Rust\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Rust</div>\n\nCreate `src/main.rs`:\n\n```rust\nuse std::sync::Arc;\nuse std::time::Duration;\n\nuse github_copilot_sdk::handler::ApproveAllHandler;\nuse github_copilot_sdk::{Client, ClientOptions, MessageOptions, SessionConfig};\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let client = Client::start(ClientOptions::default()).await?;\n    let session = client\n        .create_session(SessionConfig::default().with_permission_handler(Arc::new(ApproveAllHandler)))\n        .await?;\n\n    let response = session\n        .send_and_wait(\n            MessageOptions::new(\"What is 2 + 2?\").with_wait_timeout(Duration::from_secs(120)),\n        )\n        .await?;\n\n    if let Some(event) = response {\n        if let Some(content) = event.data.get(\"content\").and_then(|value| value.as_str()) {\n            println!(\"{content}\");\n        }\n    }\n\n    session.disconnect().await?;\n    client.stop().await?;\n    Ok(())\n}\n```\n\nRun it:\n\n```bash\ncargo run\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\nCreate a new console project and add this to `Program.cs`:\n\n```csharp\nusing GitHub.Copilot;\n\nawait using var client = new CopilotClient();\nawait using var session = await client.CreateSessionAsync(new SessionConfig\n{\n    Model = \"auto\",\n    OnPermissionRequest = PermissionHandler.ApproveAll\n});\n\nvar response = await session.SendAndWaitAsync(new MessageOptions { Prompt = \"What is 2 + 2?\" });\nConsole.WriteLine(response?.Data.Content);\n```\n\nRun it:\n\n```bash\ndotnet run\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\nCreate `HelloCopilot.java`:\n\n<!-- docs-validate: skip -->\n\n```java\nimport com.github.copilot.CopilotClient;\nimport com.github.copilot.rpc.*;\n\npublic class HelloCopilot {\n    public static void main(String[] args) throws Exception {\n        try (var client = new CopilotClient()) {\n            client.start().get();\n\n            var session = client.createSession(\n                new SessionConfig()\n                    .setModel(\"auto\")\n                    .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)\n            ).get();\n\n            var response = session.sendAndWait(\n                new MessageOptions().setPrompt(\"What is 2 + 2?\")\n            ).get();\n\n            System.out.println(response.getData().content());\n\n            client.stop().get();\n        }\n    }\n}\n```\n\nRun it:\n\n```bash\njavac -cp copilot-sdk.jar HelloCopilot.java && java -cp .:copilot-sdk.jar HelloCopilot\n```\n\n</div>\n\n</div>\n\n**You should see:**\n\n```text\n4\n```\n\nCongratulations! You just built your first Copilot-powered app.\n\n## Step 3: add streaming responses\n\nRight now, you wait for the complete response before seeing anything. Let's make it interactive by streaming the response as it's generated.\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\nUpdate `index.ts`:\n\n```typescript\nimport { CopilotClient } from \"@github/copilot-sdk\";\n\nconst client = new CopilotClient();\nconst session = await client.createSession({\n    model: \"auto\",\n    streaming: true,\n});\n\n// Listen for response chunks\nsession.on(\"assistant.message_delta\", (event) => {\n    process.stdout.write(event.data.deltaContent);\n});\nsession.on(\"session.idle\", () => {\n    console.log(); // New line when done\n});\n\nawait session.sendAndWait({ prompt: \"Tell me a short joke\" });\n\nawait client.stop();\nprocess.exit(0);\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\nUpdate `main.py`:\n\n```python\nimport asyncio\nimport sys\nfrom copilot import CopilotClient\nfrom copilot.session import PermissionHandler\nfrom copilot.session_events import SessionEventType\n\nasync def main():\n    client = CopilotClient()\n    await client.start()\n\n    session = await client.create_session(on_permission_request=PermissionHandler.approve_all, model=\"auto\", streaming=True)\n\n    # Listen for response chunks\n    def handle_event(event):\n        if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA:\n            sys.stdout.write(event.data.delta_content)\n            sys.stdout.flush()\n        if event.type == SessionEventType.SESSION_IDLE:\n            print()  # New line when done\n\n    session.on(handle_event)\n\n    await session.send_and_wait(\"Tell me a short joke\")\n\n    await client.stop()\n\nasyncio.run(main())\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\nUpdate `main.go`:\n\n```golang\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\tcopilot \"github.com/github/copilot-sdk/go\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\tclient := copilot.NewClient(nil)\n\tif err := client.Start(ctx); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer client.Stop()\n\n\tsession, err := client.CreateSession(ctx, &copilot.SessionConfig{\n\t\tModel:     \"auto\",\n\t\tStreaming: copilot.Bool(true),\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Listen for response chunks\n\tsession.On(func(event copilot.SessionEvent) {\n\t\tswitch d := event.Data.(type) {\n\t\tcase *copilot.AssistantMessageDeltaData:\n\t\t\tfmt.Print(d.DeltaContent)\n\t\tcase *copilot.SessionIdleData:\n\t\t\t_ = d\n\t\t\tfmt.Println()\n\t\t}\n\t})\n\n\t_, err = session.SendAndWait(ctx, copilot.MessageOptions{Prompt: \"Tell me a short joke\"})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tos.Exit(0)\n}\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"rust\" data-label=\"Rust\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Rust</div>\n\nUpdate `src/main.rs`:\n\n```rust\nuse std::io::{self, Write};\nuse std::sync::Arc;\nuse std::time::Duration;\n\nuse github_copilot_sdk::handler::ApproveAllHandler;\nuse github_copilot_sdk::{Client, ClientOptions, MessageOptions, SessionConfig};\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let client = Client::start(ClientOptions::default()).await?;\n\n    let mut config = SessionConfig::default();\n    config.streaming = Some(true);\n    let session = client\n        .create_session(config.with_permission_handler(Arc::new(ApproveAllHandler)))\n        .await?;\n\n    // Listen for response chunks\n    let mut events = session.subscribe();\n    tokio::spawn(async move {\n        while let Ok(event) = events.recv().await {\n            match event.event_type.as_str() {\n                \"assistant.message_delta\" => {\n                    if let Some(text) =\n                        event.data.get(\"deltaContent\").and_then(|value| value.as_str())\n                    {\n                        print!(\"{text}\");\n                        io::stdout().flush().ok();\n                    }\n                }\n                \"assistant.message\" => println!(),\n                _ => {}\n            }\n        }\n    });\n\n    session\n        .send_and_wait(\n            MessageOptions::new(\"Tell me a short joke\")\n                .with_wait_timeout(Duration::from_secs(120)),\n        )\n        .await?;\n\n    session.disconnect().await?;\n    client.stop().await?;\n    Ok(())\n}\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\nUpdate `Program.cs`:\n\n```csharp\nusing GitHub.Copilot;\n\nawait using var client = new CopilotClient();\nawait using var session = await client.CreateSessionAsync(new SessionConfig\n{\n    Model = \"auto\",\n    OnPermissionRequest = PermissionHandler.ApproveAll,\n    Streaming = true,\n});\n\n// Listen for response chunks\nsession.On<SessionEvent>(ev =>\n{\n    if (ev is AssistantMessageDeltaEvent deltaEvent)\n    {\n        Console.Write(deltaEvent.Data.DeltaContent);\n    }\n    if (ev is SessionIdleEvent)\n    {\n        Console.WriteLine();\n    }\n});\n\nawait session.SendAndWaitAsync(new MessageOptions { Prompt = \"Tell me a short joke\" });\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\nUpdate `HelloCopilot.java`:\n\n<!-- docs-validate: skip -->\n\n```java\nimport com.github.copilot.CopilotClient;\nimport com.github.copilot.rpc.*;\n\npublic class HelloCopilot {\n    public static void main(String[] args) throws Exception {\n        try (var client = new CopilotClient()) {\n            client.start().get();\n\n            var session = client.createSession(\n                new SessionConfig()\n                    .setModel(\"auto\")\n                    .setStreaming(true)\n                    .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)\n            ).get();\n\n            // Listen for response chunks\n            session.on(AssistantMessageDeltaEvent.class, delta -> {\n                System.out.print(delta.getData().deltaContent());\n            });\n            session.on(SessionIdleEvent.class, idle -> {\n                System.out.println(); // New line when done\n            });\n\n            session.sendAndWait(\n                new MessageOptions().setPrompt(\"Tell me a short joke\")\n            ).get();\n\n            client.stop().get();\n        }\n    }\n}\n```\n\n</div>\n\n</div>\n\nRun the code again. You'll see the response appear word by word.\n\n### Event subscription methods\n\nThe SDK provides methods for subscribing to session events:\n\n| Method                   | Description                                                                              |\n| ------------------------ | ---------------------------------------------------------------------------------------- |\n| `on(handler)`            | Subscribe to all events; returns unsubscribe function                                    |\n| `on(eventType, handler)` | Subscribe to specific event type (Node.js/TypeScript only); returns unsubscribe function |\n| `subscribe()`            | Subscribe to all events (Rust); filter by `event_type`                                   |\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\n// Subscribe to all events\nconst unsubscribeAll = session.on((event) => {\n    console.log(\"Event:\", event.type);\n});\n\n// Subscribe to specific event type\nconst unsubscribeIdle = session.on(\"session.idle\", (event) => {\n    console.log(\"Session is idle\");\n});\n\n// Later, to unsubscribe:\nunsubscribeAll();\nunsubscribeIdle();\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n```python\nfrom copilot import CopilotClient, PermissionDecisionApproveOnce\nfrom copilot.session_events import SessionEvent, SessionEventType\n\nclient = CopilotClient()\n\nsession = await client.create_session(on_permission_request=lambda req, inv: PermissionDecisionApproveOnce())\n\n# Subscribe to all events\nunsubscribe = session.on(lambda event: print(f\"Event: {event.type}\"))\n\n# Filter by event type in your handler\ndef handle_event(event: SessionEvent) -> None:\n    if event.type == SessionEventType.SESSION_IDLE:\n        print(\"Session is idle\")\n    elif event.type == SessionEventType.ASSISTANT_MESSAGE:\n        print(f\"Message: {event.data.content}\")\n\nunsubscribe = session.on(handle_event)\n\n# Later, to unsubscribe:\nunsubscribe()\n```\n\n```python\n# Subscribe to all events\nunsubscribe = session.on(lambda event: print(f\"Event: {event.type}\"))\n\n# Filter by event type in your handler\ndef handle_event(event):\n    if event.type == SessionEventType.SESSION_IDLE:\n        print(\"Session is idle\")\n    elif event.type == SessionEventType.ASSISTANT_MESSAGE:\n        print(f\"Message: {event.data.content}\")\n\nunsubscribe = session.on(handle_event)\n\n# Later, to unsubscribe:\nunsubscribe()\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n```golang\npackage main\n\nimport (\n\t\"fmt\"\n\n\tcopilot \"github.com/github/copilot-sdk/go\"\n)\n\nfunc main() {\n\tsession := &copilot.Session{}\n\n\t// Subscribe to all events\n\tunsubscribe := session.On(func(event copilot.SessionEvent) {\n\t\tfmt.Println(\"Event:\", event.Type)\n\t})\n\n\t// Filter by event type in your handler\n\tsession.On(func(event copilot.SessionEvent) {\n\t\tswitch d := event.Data.(type) {\n\t\tcase *copilot.SessionIdleData:\n\t\t\t_ = d\n\t\t\tfmt.Println(\"Session is idle\")\n\t\tcase *copilot.AssistantMessageData:\n\t\t\tfmt.Println(\"Message:\", d.Content)\n\t\t}\n\t})\n\n\t// Later, to unsubscribe:\n\tunsubscribe()\n}\n```\n\n```golang\n// Subscribe to all events\nunsubscribe := session.On(func(event copilot.SessionEvent) {\n    fmt.Println(\"Event:\", event.Type)\n})\n\n// Filter by event type in your handler\nsession.On(func(event copilot.SessionEvent) {\n    switch d := event.Data.(type) {\n    case *copilot.SessionIdleData:\n        _ = d\n        fmt.Println(\"Session is idle\")\n    case *copilot.AssistantMessageData:\n        fmt.Println(\"Message:\", d.Content)\n    }\n})\n\n// Later, to unsubscribe:\nunsubscribe()\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"rust\" data-label=\"Rust\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Rust</div>\n\n```rust\nlet mut events = session.subscribe();\n\ntokio::spawn(async move {\n    while let Ok(event) = events.recv().await {\n        println!(\"Event: {}\", event.event_type);\n\n        match event.event_type.as_str() {\n            \"session.idle\" => println!(\"Session is idle\"),\n            \"assistant.message\" => {\n                if let Some(content) = event.data.get(\"content\").and_then(|value| value.as_str()) {\n                    println!(\"Message: {content}\");\n                }\n            }\n            _ => {}\n        }\n    }\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n```csharp\nusing GitHub.Copilot;\n\npublic static class EventSubscriptionExample\n{\n    public static void Example(CopilotSession session)\n    {\n        // Subscribe to all events\n        var unsubscribe = session.On<SessionEvent>(ev => Console.WriteLine($\"Event: {ev.Type}\"));\n\n        // Filter by event type using pattern matching\n        session.On<SessionEvent>(ev =>\n        {\n            switch (ev)\n            {\n                case SessionIdleEvent:\n                    Console.WriteLine(\"Session is idle\");\n                    break;\n                case AssistantMessageEvent msg:\n                    Console.WriteLine($\"Message: {msg.Data.Content}\");\n                    break;\n            }\n        });\n\n        // Later, to unsubscribe:\n        unsubscribe.Dispose();\n    }\n}\n```\n\n```csharp\n// Subscribe to all events\nvar unsubscribe = session.On<SessionEvent>(ev => Console.WriteLine($\"Event: {ev.Type}\"));\n\n// Filter by event type using pattern matching\nsession.On<SessionEvent>(ev =>\n{\n    switch (ev)\n    {\n        case SessionIdleEvent:\n            Console.WriteLine(\"Session is idle\");\n            break;\n        case AssistantMessageEvent msg:\n            Console.WriteLine($\"Message: {msg.Data.Content}\");\n            break;\n    }\n});\n\n// Later, to unsubscribe:\nunsubscribe.Dispose();\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n<!-- docs-validate: skip -->\n\n```java\n// Subscribe to all events\nvar unsubscribe = session.on(event -> {\n    System.out.println(\"Event: \" + event.getType());\n});\n\n// Subscribe to a specific event type\nsession.on(AssistantMessageEvent.class, msg -> {\n    System.out.println(\"Message: \" + msg.getData().content());\n});\n\nsession.on(SessionIdleEvent.class, idle -> {\n    System.out.println(\"Session is idle\");\n});\n\n// Later, to unsubscribe:\nunsubscribe.close();\n```\n\n</div>\n\n</div>\n\n## Step 4: add a custom tool\n\nNow 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.\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\nUpdate `index.ts`:\n\n```typescript\nimport { CopilotClient, defineTool } from \"@github/copilot-sdk\";\n\n// Define a tool that Copilot can call\nconst getWeather = defineTool(\"get_weather\", {\n    description: \"Get the current weather for a city\",\n    parameters: {\n        type: \"object\",\n        properties: {\n            city: { type: \"string\", description: \"The city name\" },\n        },\n        required: [\"city\"],\n    },\n    handler: async (args: { city: string }) => {\n        const { city } = args;\n        // In a real app, you'd call a weather API here\n        const conditions = [\"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\"];\n        const temp = Math.floor(Math.random() * 30) + 50;\n        const condition = conditions[Math.floor(Math.random() * conditions.length)];\n        return { city, temperature: `${temp}°F`, condition };\n    },\n});\n\nconst client = new CopilotClient();\nconst session = await client.createSession({\n    model: \"auto\",\n    streaming: true,\n    tools: [getWeather],\n});\n\nsession.on(\"assistant.message_delta\", (event) => {\n    process.stdout.write(event.data.deltaContent);\n});\n\nsession.on(\"session.idle\", () => {\n    console.log(); // New line when done\n});\n\nawait session.sendAndWait({\n    prompt: \"What's the weather like in Seattle and Tokyo?\",\n});\n\nawait client.stop();\nprocess.exit(0);\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\nUpdate `main.py`:\n\n```python\nimport asyncio\nimport random\nimport sys\nfrom copilot import CopilotClient\nfrom copilot.session import PermissionHandler\nfrom copilot.tools import define_tool\nfrom copilot.session_events import SessionEventType\nfrom pydantic import BaseModel, Field\n\n# Define the parameters for the tool using Pydantic\nclass GetWeatherParams(BaseModel):\n    city: str = Field(description=\"The name of the city to get weather for\")\n\n# Define a tool that Copilot can call\n@define_tool(description=\"Get the current weather for a city\")\nasync def get_weather(params: GetWeatherParams) -> dict:\n    city = params.city\n    # In a real app, you'd call a weather API here\n    conditions = [\"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\"]\n    temp = random.randint(50, 80)\n    condition = random.choice(conditions)\n    return {\"city\": city, \"temperature\": f\"{temp}°F\", \"condition\": condition}\n\nasync def main():\n    client = CopilotClient()\n    await client.start()\n\n    session = await client.create_session(on_permission_request=PermissionHandler.approve_all, model=\"auto\", streaming=True, tools=[get_weather])\n\n    def handle_event(event):\n        if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA:\n            sys.stdout.write(event.data.delta_content)\n            sys.stdout.flush()\n        if event.type == SessionEventType.SESSION_IDLE:\n            print()\n\n    session.on(handle_event)\n\n    await session.send_and_wait(\"What's the weather like in Seattle and Tokyo?\")\n\n    await client.stop()\n\nasyncio.run(main())\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\nUpdate `main.go`:\n\n```golang\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"math/rand\"\n\t\"os\"\n\n\tcopilot \"github.com/github/copilot-sdk/go\"\n)\n\n// Define the parameter type\ntype WeatherParams struct {\n\tCity string `json:\"city\" jsonschema:\"The city name\"`\n}\n\n// Define the return type\ntype WeatherResult struct {\n\tCity        string `json:\"city\"`\n\tTemperature string `json:\"temperature\"`\n\tCondition   string `json:\"condition\"`\n}\n\nfunc main() {\n\tctx := context.Background()\n\n\t// Define a tool that Copilot can call\n\tgetWeather := copilot.DefineTool(\n\t\t\"get_weather\",\n\t\t\"Get the current weather for a city\",\n\t\tfunc(params WeatherParams, inv copilot.ToolInvocation) (WeatherResult, error) {\n\t\t\t// In a real app, you'd call a weather API here\n\t\t\tconditions := []string{\"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\"}\n\t\t\ttemp := rand.Intn(30) + 50\n\t\t\tcondition := conditions[rand.Intn(len(conditions))]\n\t\t\treturn WeatherResult{\n\t\t\t\tCity:        params.City,\n\t\t\t\tTemperature: fmt.Sprintf(\"%d°F\", temp),\n\t\t\t\tCondition:   condition,\n\t\t\t}, nil\n\t\t},\n\t)\n\n\tclient := copilot.NewClient(nil)\n\tif err := client.Start(ctx); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer client.Stop()\n\n\tsession, err := client.CreateSession(ctx, &copilot.SessionConfig{\n\t\tModel:     \"auto\",\n\t\tStreaming: copilot.Bool(true),\n\t\tTools:     []copilot.Tool{getWeather},\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tsession.On(func(event copilot.SessionEvent) {\n\t\tswitch d := event.Data.(type) {\n\t\tcase *copilot.AssistantMessageDeltaData:\n\t\t\tfmt.Print(d.DeltaContent)\n\t\tcase *copilot.SessionIdleData:\n\t\t\t_ = d\n\t\t\tfmt.Println()\n\t\t}\n\t})\n\n\t_, err = session.SendAndWait(ctx, copilot.MessageOptions{\n\t\tPrompt: \"What's the weather like in Seattle and Tokyo?\",\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tos.Exit(0)\n}\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"rust\" data-label=\"Rust\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Rust</div>\n\nUpdate `src/main.rs`:\n\n```rust\nuse std::io::{self, Write};\nuse std::sync::Arc;\nuse std::time::Duration;\n\nuse github_copilot_sdk::handler::ApproveAllHandler;\nuse github_copilot_sdk::tool::{define_tool, JsonSchema};\nuse github_copilot_sdk::{Client, ClientOptions, MessageOptions, SessionConfig, ToolResult};\nuse serde::Deserialize;\n\n#[derive(Deserialize, JsonSchema)]\nstruct GetWeatherParams {\n    city: String,\n}\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    // Define a tool that Copilot can call\n    let tools = vec![define_tool(\n        \"get_weather\",\n        \"Get the current weather for a city\",\n        |_inv, params: GetWeatherParams| async move {\n            Ok(ToolResult::Text(format!(\n                \"{}: 62°F and sunny\",\n                params.city\n            )))\n        },\n    )];\n\n    let client = Client::start(ClientOptions::default()).await?;\n\n    let mut config = SessionConfig::default();\n    config.streaming = Some(true);\n    let session = client\n        .create_session(\n            config\n                .with_tools(tools)\n                .with_permission_handler(Arc::new(ApproveAllHandler)),\n        )\n        .await?;\n\n    let mut events = session.subscribe();\n    tokio::spawn(async move {\n        while let Ok(event) = events.recv().await {\n            match event.event_type.as_str() {\n                \"assistant.message_delta\" => {\n                    if let Some(text) =\n                        event.data.get(\"deltaContent\").and_then(|value| value.as_str())\n                    {\n                        print!(\"{text}\");\n                        io::stdout().flush().ok();\n                    }\n                }\n                \"assistant.message\" => println!(),\n                _ => {}\n            }\n        }\n    });\n\n    session\n        .send_and_wait(\n            MessageOptions::new(\"What's the weather like in Seattle and Tokyo?\")\n                .with_wait_timeout(Duration::from_secs(120)),\n        )\n        .await?;\n\n    session.disconnect().await?;\n    client.stop().await?;\n    Ok(())\n}\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\nUpdate `Program.cs`:\n\n```csharp\nusing GitHub.Copilot;\nusing Microsoft.Extensions.AI;\nusing System.ComponentModel;\n\nawait using var client = new CopilotClient();\n\n// Define a tool that Copilot can call\nvar getWeather = CopilotTool.DefineTool(\n    ([Description(\"The city name\")] string city) =>\n    {\n        // In a real app, you'd call a weather API here\n        var conditions = new[] { \"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\" };\n        var temp = Random.Shared.Next(50, 80);\n        var condition = conditions[Random.Shared.Next(conditions.Length)];\n        return new { city, temperature = $\"{temp}°F\", condition };\n    },\n    factoryOptions: new AIFunctionFactoryOptions\n    {\n        Name = \"get_weather\",\n        Description = \"Get the current weather for a city\",\n    }\n);\n\nawait using var session = await client.CreateSessionAsync(new SessionConfig\n{\n    Model = \"auto\",\n    OnPermissionRequest = PermissionHandler.ApproveAll,\n    Streaming = true,\n    Tools = [getWeather],\n});\n\nsession.On<SessionEvent>(ev =>\n{\n    if (ev is AssistantMessageDeltaEvent deltaEvent)\n    {\n        Console.Write(deltaEvent.Data.DeltaContent);\n    }\n    if (ev is SessionIdleEvent)\n    {\n        Console.WriteLine();\n    }\n});\n\nawait session.SendAndWaitAsync(new MessageOptions\n{\n    Prompt = \"What's the weather like in Seattle and Tokyo?\",\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\nUpdate `HelloCopilot.java`:\n\n<!-- docs-validate: skip -->\n\n```java\nimport com.github.copilot.CopilotClient;\nimport com.github.copilot.rpc.*;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.concurrent.CompletableFuture;\n\npublic class HelloCopilot {\n    public static void main(String[] args) throws Exception {\n        var random = new Random();\n        var conditions = List.of(\"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\");\n\n        // Define a tool that Copilot can call\n        var getWeather = ToolDefinition.create(\n            \"get_weather\",\n            \"Get the current weather for a city\",\n            Map.of(\n                \"type\", \"object\",\n                \"properties\", Map.of(\n                    \"city\", Map.of(\"type\", \"string\", \"description\", \"The city name\")\n                ),\n                \"required\", List.of(\"city\")\n            ),\n            invocation -> {\n                var city = (String) invocation.getArguments().get(\"city\");\n                var temp = random.nextInt(30) + 50;\n                var condition = conditions.get(random.nextInt(conditions.size()));\n                return CompletableFuture.completedFuture(Map.of(\n                    \"city\", city,\n                    \"temperature\", temp + \"°F\",\n                    \"condition\", condition\n                ));\n            }\n        );\n\n        try (var client = new CopilotClient()) {\n            client.start().get();\n\n            var session = client.createSession(\n                new SessionConfig()\n                    .setModel(\"auto\")\n                    .setStreaming(true)\n                    .setTools(List.of(getWeather))\n                    .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)\n            ).get();\n\n            session.on(AssistantMessageDeltaEvent.class, delta -> {\n                System.out.print(delta.getData().deltaContent());\n            });\n            session.on(SessionIdleEvent.class, idle -> {\n                System.out.println();\n            });\n\n            session.sendAndWait(\n                new MessageOptions().setPrompt(\"What's the weather like in Seattle and Tokyo?\")\n            ).get();\n\n            client.stop().get();\n        }\n    }\n}\n```\n\n</div>\n\n</div>\n\nRun it and you'll see Copilot call your tool to get weather data, then respond with the results!\n\n## Step 5: build an interactive assistant\n\nLet's put it all together into a useful interactive assistant:\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\nimport { CopilotClient, defineTool } from \"@github/copilot-sdk\";\nimport * as readline from \"readline\";\n\nconst getWeather = defineTool(\"get_weather\", {\n    description: \"Get the current weather for a city\",\n    parameters: {\n        type: \"object\",\n        properties: {\n            city: { type: \"string\", description: \"The city name\" },\n        },\n        required: [\"city\"],\n    },\n    handler: async ({ city }) => {\n        const conditions = [\"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\"];\n        const temp = Math.floor(Math.random() * 30) + 50;\n        const condition = conditions[Math.floor(Math.random() * conditions.length)];\n        return { city, temperature: `${temp}°F`, condition };\n    },\n});\n\nconst client = new CopilotClient();\nconst session = await client.createSession({\n    model: \"auto\",\n    streaming: true,\n    tools: [getWeather],\n});\n\nsession.on(\"assistant.message_delta\", (event) => {\n    process.stdout.write(event.data.deltaContent);\n});\n\nconst rl = readline.createInterface({\n    input: process.stdin,\n    output: process.stdout,\n});\n\nconsole.log(\"🌤️  Weather Assistant (type 'exit' to quit)\");\nconsole.log(\"   Try: 'What's the weather in Paris?'\\n\");\n\nconst prompt = () => {\n    rl.question(\"You: \", async (input) => {\n        if (input.toLowerCase() === \"exit\") {\n            await client.stop();\n            rl.close();\n            return;\n        }\n\n        process.stdout.write(\"Assistant: \");\n        await session.sendAndWait({ prompt: input });\n        console.log(\"\\n\");\n        prompt();\n    });\n};\n\nprompt();\n```\n\nRun with:\n\n```bash\nnpx tsx weather-assistant.ts\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\nCreate `weather_assistant.py`:\n\n```python\nimport asyncio\nimport random\nimport sys\nfrom copilot import CopilotClient\nfrom copilot.session import PermissionHandler\nfrom copilot.tools import define_tool\nfrom copilot.session_events import SessionEventType\nfrom pydantic import BaseModel, Field\n\nclass GetWeatherParams(BaseModel):\n    city: str = Field(description=\"The name of the city to get weather for\")\n\n@define_tool(description=\"Get the current weather for a city\")\nasync def get_weather(params: GetWeatherParams) -> dict:\n    city = params.city\n    conditions = [\"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\"]\n    temp = random.randint(50, 80)\n    condition = random.choice(conditions)\n    return {\"city\": city, \"temperature\": f\"{temp}°F\", \"condition\": condition}\n\nasync def main():\n    client = CopilotClient()\n    await client.start()\n\n    session = await client.create_session(on_permission_request=PermissionHandler.approve_all, model=\"auto\", streaming=True, tools=[get_weather])\n\n    def handle_event(event):\n        if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA:\n            sys.stdout.write(event.data.delta_content)\n            sys.stdout.flush()\n\n    session.on(handle_event)\n\n    print(\"🌤️  Weather Assistant (type 'exit' to quit)\")\n    print(\"   Try: 'What's the weather in Paris?' or 'Compare weather in NYC and LA'\\n\")\n\n    while True:\n        try:\n            user_input = input(\"You: \")\n        except EOFError:\n            break\n\n        if user_input.lower() == \"exit\":\n            break\n\n        sys.stdout.write(\"Assistant: \")\n        await session.send_and_wait(user_input)\n        print(\"\\n\")\n\n    await client.stop()\n\nasyncio.run(main())\n```\n\nRun with:\n\n```bash\npython weather_assistant.py\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\nCreate `weather-assistant.go`:\n\n```golang\npackage main\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"math/rand\"\n\t\"os\"\n\t\"strings\"\n\n\tcopilot \"github.com/github/copilot-sdk/go\"\n)\n\ntype WeatherParams struct {\n\tCity string `json:\"city\" jsonschema:\"The city name\"`\n}\n\ntype WeatherResult struct {\n\tCity        string `json:\"city\"`\n\tTemperature string `json:\"temperature\"`\n\tCondition   string `json:\"condition\"`\n}\n\nfunc main() {\n\tctx := context.Background()\n\n\tgetWeather := copilot.DefineTool(\n\t\t\"get_weather\",\n\t\t\"Get the current weather for a city\",\n\t\tfunc(params WeatherParams, inv copilot.ToolInvocation) (WeatherResult, error) {\n\t\t\tconditions := []string{\"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\"}\n\t\t\ttemp := rand.Intn(30) + 50\n\t\t\tcondition := conditions[rand.Intn(len(conditions))]\n\t\t\treturn WeatherResult{\n\t\t\t\tCity:        params.City,\n\t\t\t\tTemperature: fmt.Sprintf(\"%d°F\", temp),\n\t\t\t\tCondition:   condition,\n\t\t\t}, nil\n\t\t},\n\t)\n\n\tclient := copilot.NewClient(nil)\n\tif err := client.Start(ctx); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer client.Stop()\n\n\tsession, err := client.CreateSession(ctx, &copilot.SessionConfig{\n\t\tModel:     \"auto\",\n\t\tStreaming: copilot.Bool(true),\n\t\tTools:     []copilot.Tool{getWeather},\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tsession.On(func(event copilot.SessionEvent) {\n\t\tswitch d := event.Data.(type) {\n\t\tcase *copilot.AssistantMessageDeltaData:\n\t\t\tfmt.Print(d.DeltaContent)\n\t\tcase *copilot.SessionIdleData:\n\t\t\t_ = d\n\t\t\tfmt.Println()\n\t\t}\n\t})\n\n\tfmt.Println(\"🌤️  Weather Assistant (type 'exit' to quit)\")\n\tfmt.Println(\"   Try: 'What's the weather in Paris?' or 'Compare weather in NYC and LA'\\n\")\n\n\tscanner := bufio.NewScanner(os.Stdin)\n\tfor {\n\t\tfmt.Print(\"You: \")\n\t\tif !scanner.Scan() {\n\t\t\tbreak\n\t\t}\n\t\tinput := scanner.Text()\n\t\tif strings.ToLower(input) == \"exit\" {\n\t\t\tbreak\n\t\t}\n\n\t\tfmt.Print(\"Assistant: \")\n\t\t_, err = session.SendAndWait(ctx, copilot.MessageOptions{Prompt: input})\n\t\tif err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"Error: %v\\n\", err)\n\t\t\tbreak\n\t\t}\n\t\tfmt.Println()\n\t}\n\tif err := scanner.Err(); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Input error: %v\\n\", err)\n\t}\n}\n```\n\nRun with:\n\n```bash\ngo run weather-assistant.go\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"rust\" data-label=\"Rust\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Rust</div>\n\nCreate `src/main.rs`:\n\n```rust\nuse std::io::{self, BufRead, Write};\nuse std::sync::Arc;\nuse std::time::Duration;\n\nuse github_copilot_sdk::handler::ApproveAllHandler;\nuse github_copilot_sdk::tool::{define_tool, JsonSchema};\nuse github_copilot_sdk::{Client, ClientOptions, MessageOptions, SessionConfig, ToolResult};\nuse serde::Deserialize;\n\n#[derive(Deserialize, JsonSchema)]\nstruct GetWeatherParams {\n    city: String,\n}\n\nfn read_line() -> Option<String> {\n    let stdin = io::stdin();\n    let mut line = String::new();\n    stdin.lock().read_line(&mut line).ok()?;\n    if line.is_empty() {\n        return None;\n    }\n    Some(line.trim_end_matches(&['\\n', '\\r'][..]).to_string())\n}\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let tools = vec![define_tool(\n        \"get_weather\",\n        \"Get the current weather for a city\",\n        |_inv, params: GetWeatherParams| async move {\n            Ok(ToolResult::Text(format!(\n                \"{}: 62°F and sunny\",\n                params.city\n            )))\n        },\n    )];\n\n    let client = Client::start(ClientOptions::default()).await?;\n\n    let mut config = SessionConfig::default();\n    config.streaming = Some(true);\n    let session = client\n        .create_session(\n            config\n                .with_tools(tools)\n                .with_permission_handler(Arc::new(ApproveAllHandler)),\n        )\n        .await?;\n\n    let mut events = session.subscribe();\n    tokio::spawn(async move {\n        while let Ok(event) = events.recv().await {\n            match event.event_type.as_str() {\n                \"assistant.message_delta\" => {\n                    if let Some(text) =\n                        event.data.get(\"deltaContent\").and_then(|value| value.as_str())\n                    {\n                        print!(\"{text}\");\n                        io::stdout().flush().ok();\n                    }\n                }\n                \"assistant.message\" => println!(),\n                _ => {}\n            }\n        }\n    });\n\n    println!(\"Weather Assistant (type 'exit' to quit)\");\n    println!(\"Try: 'What's the weather in Paris?' or 'Compare weather in NYC and LA'\\n\");\n\n    loop {\n        print!(\"You: \");\n        io::stdout().flush().ok();\n\n        let Some(input) = read_line() else { break };\n        if input.eq_ignore_ascii_case(\"exit\") {\n            break;\n        }\n\n        print!(\"Assistant: \");\n        io::stdout().flush().ok();\n        session\n            .send_and_wait(MessageOptions::new(input).with_wait_timeout(Duration::from_secs(120)))\n            .await?;\n        println!();\n    }\n\n    session.disconnect().await?;\n    client.stop().await?;\n    Ok(())\n}\n```\n\nRun with:\n\n```bash\ncargo run\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\nCreate a new console project and update `Program.cs`:\n\n```csharp\nusing GitHub.Copilot;\nusing Microsoft.Extensions.AI;\nusing System.ComponentModel;\n\n// Define the weather tool\nvar getWeather = CopilotTool.DefineTool(\n    ([Description(\"The city name\")] string city) =>\n    {\n        var conditions = new[] { \"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\" };\n        var temp = Random.Shared.Next(50, 80);\n        var condition = conditions[Random.Shared.Next(conditions.Length)];\n        return new { city, temperature = $\"{temp}°F\", condition };\n    },\n    factoryOptions: new AIFunctionFactoryOptions\n    {\n        Name = \"get_weather\",\n        Description = \"Get the current weather for a city\",\n    });\n\nawait using var client = new CopilotClient();\nawait using var session = await client.CreateSessionAsync(new SessionConfig\n{\n    Model = \"auto\",\n    OnPermissionRequest = PermissionHandler.ApproveAll,\n    Streaming = true,\n    Tools = [getWeather]\n});\n\n// Listen for response chunks\nsession.On<SessionEvent>(ev =>\n{\n    if (ev is AssistantMessageDeltaEvent deltaEvent)\n    {\n        Console.Write(deltaEvent.Data.DeltaContent);\n    }\n    if (ev is SessionIdleEvent)\n    {\n        Console.WriteLine();\n    }\n});\n\nConsole.WriteLine(\"🌤️  Weather Assistant (type 'exit' to quit)\");\nConsole.WriteLine(\"   Try: 'What's the weather in Paris?' or 'Compare weather in NYC and LA'\\n\");\n\nwhile (true)\n{\n    Console.Write(\"You: \");\n    var input = Console.ReadLine();\n\n    if (string.IsNullOrEmpty(input) || input.Equals(\"exit\", StringComparison.OrdinalIgnoreCase))\n    {\n        break;\n    }\n\n    Console.Write(\"Assistant: \");\n    await session.SendAndWaitAsync(new MessageOptions { Prompt = input });\n    Console.WriteLine(\"\\n\");\n}\n```\n\nRun with:\n\n```bash\ndotnet run\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\nCreate `WeatherAssistant.java`:\n\n<!-- docs-validate: skip -->\n\n```java\nimport com.github.copilot.CopilotClient;\nimport com.github.copilot.rpc.*;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.Scanner;\nimport java.util.concurrent.CompletableFuture;\n\npublic class WeatherAssistant {\n    public static void main(String[] args) throws Exception {\n        var random = new Random();\n        var conditions = List.of(\"sunny\", \"cloudy\", \"rainy\", \"partly cloudy\");\n\n        var getWeather = ToolDefinition.create(\n            \"get_weather\",\n            \"Get the current weather for a city\",\n            Map.of(\n                \"type\", \"object\",\n                \"properties\", Map.of(\n                    \"city\", Map.of(\"type\", \"string\", \"description\", \"The city name\")\n                ),\n                \"required\", List.of(\"city\")\n            ),\n            invocation -> {\n                var city = (String) invocation.getArguments().get(\"city\");\n                var temp = random.nextInt(30) + 50;\n                var condition = conditions.get(random.nextInt(conditions.size()));\n                return CompletableFuture.completedFuture(Map.of(\n                    \"city\", city,\n                    \"temperature\", temp + \"°F\",\n                    \"condition\", condition\n                ));\n            }\n        );\n\n        try (var client = new CopilotClient()) {\n            client.start().get();\n\n            var session = client.createSession(\n                new SessionConfig()\n                    .setModel(\"auto\")\n                    .setStreaming(true)\n                    .setOnPermissionRequest(request ->\n                        CompletableFuture.completedFuture(PermissionDecision.allow())\n                    )\n                    .setTools(List.of(getWeather))\n            ).get();\n\n            session.on(AssistantMessageDeltaEvent.class, delta -> {\n                System.out.print(delta.getData().deltaContent());\n            });\n            session.on(SessionIdleEvent.class, idle -> {\n                System.out.println();\n            });\n\n            System.out.println(\"🌤️  Weather Assistant (type 'exit' to quit)\");\n            System.out.println(\"   Try: 'What's the weather in Paris?' or 'Compare weather in NYC and LA'\\n\");\n\n            var scanner = new Scanner(System.in);\n            while (true) {\n                System.out.print(\"You: \");\n                if (!scanner.hasNextLine()) break;\n                var input = scanner.nextLine();\n                if (input.equalsIgnoreCase(\"exit\")) break;\n\n                System.out.print(\"Assistant: \");\n                session.sendAndWait(\n                    new MessageOptions().setPrompt(input)\n                ).get();\n                System.out.println(\"\\n\");\n            }\n\n            client.stop().get();\n        }\n    }\n}\n```\n\nRun with:\n\n```bash\njavac -cp copilot-sdk.jar WeatherAssistant.java && java -cp .:copilot-sdk.jar WeatherAssistant\n```\n\n</div>\n\n</div>\n\n**Example session:**\n\n```text\n🌤️  Weather Assistant (type 'exit' to quit)\n   Try: 'What's the weather in Paris?' or 'Compare weather in NYC and LA'\n\nYou: What's the weather in Seattle?\nAssistant: Let me check the weather for Seattle...\nIt's currently 62°F and cloudy in Seattle.\n\nYou: How about Tokyo and London?\nAssistant: I'll check both cities for you:\n- Tokyo: 75°F and sunny\n- London: 58°F and rainy\n\nYou: exit\n```\n\nYou've built an assistant with a custom tool that Copilot can call!\n\n## How tools work\n\nWhen you define a tool, you're telling Copilot:\n\n1. **What the tool does** (description)\n2. **What parameters it needs** (schema)\n3. **What code to run** (handler)\n\nCopilot decides when to call your tool based on the user's question. When it does:\n\n1. Copilot sends a tool call request with the parameters\n2. The SDK runs your handler function\n3. The result is sent back to Copilot\n4. Copilot incorporates the result into its response\n\n## What's next?\n\nNow that you've got the basics, here are more powerful features to explore:\n\n### Connect to MCP servers\n\nMCP (Model Context Protocol) servers provide pre-built tools. Connect to GitHub's MCP server to give Copilot access to repositories, issues, and pull requests:\n\n```typescript\nconst session = await client.createSession({\n    mcpServers: {\n        github: {\n            type: \"http\",\n            url: \"https://api.githubcopilot.com/mcp/\",\n        },\n    },\n});\n```\n\n📖 **[Using MCP servers with the GitHub Copilot SDK](/en/copilot/how-tos/copilot-sdk/features/mcp)** - Learn about local vs remote servers, all configuration options, and troubleshooting.\n\n### Create custom agents\n\nDefine specialized AI personas for specific tasks:\n\n```typescript\nconst session = await client.createSession({\n    customAgents: [{\n        name: \"pr-reviewer\",\n        displayName: \"PR Reviewer\",\n        description: \"Reviews pull requests for best practices\",\n        prompt: \"You are an expert code reviewer. Focus on security, performance, and maintainability.\",\n    }],\n});\n```\n\n> \\[!TIP]\n> You can also set `agent: \"pr-reviewer\"` in the session config to pre-select this agent from the start. See the [Custom agents and sub-agent orchestration](/en/copilot/how-tos/copilot-sdk/features/custom-agents#selecting-an-agent-at-session-creation) for details.\n\n### Customize the system message\n\nControl the AI's behavior and personality by appending instructions:\n\n```typescript\nconst session = await client.createSession({\n    systemMessage: {\n        content: \"You are a helpful assistant for our engineering team. Always be concise.\",\n    },\n});\n```\n\nFor more fine-grained control, use `mode: \"customize\"` to override individual sections of the system prompt while preserving the rest:\n\n```typescript\nconst session = await client.createSession({\n    systemMessage: {\n        mode: \"customize\",\n        sections: {\n            tone: { action: \"replace\", content: \"Respond in a warm, professional tone. Be thorough in explanations.\" },\n            code_change_rules: { action: \"remove\" },\n            guidelines: { action: \"append\", content: \"\\n* Always cite data sources\" },\n        },\n        content: \"Focus on financial analysis and reporting.\",\n    },\n});\n```\n\nAvailable section IDs: `preamble`, `identity`, `tone`, `tool_efficiency`, `environment_context`, `code_change_rules`, `guidelines`, `safety`, `tool_instructions`, `custom_instructions`, `runtime_instructions`, `last_instructions`.\n\n`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.\n\nEach 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.\n\nSee the language-specific SDK READMEs for examples in [TypeScript](https://github.com/github/copilot-sdk/tree/main/nodejs/README.md), [Python](https://github.com/github/copilot-sdk/tree/main/python/README.md), [Go](https://github.com/github/copilot-sdk/tree/main/go/README.md), [Rust](https://github.com/github/copilot-sdk/tree/main/rust/README.md), [Java](https://github.com/github/copilot-sdk/tree/main/java/README.md), and [C#](https://github.com/github/copilot-sdk/tree/main/dotnet/README.md).\n\n## Connecting to an external CLI server\n\nBy 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:\n\n* **Debugging**: Keep the CLI running between SDK restarts to inspect logs\n* **Resource sharing**: Multiple SDK clients can connect to the same CLI server\n* **Development**: Run the CLI with custom settings or in a different environment\n\n### Running the CLI in server mode\n\nStart the CLI in server mode using the `--headless` flag and optionally specify a port:\n\n```bash\ncopilot --headless --port 4321\n```\n\nIf you don't specify a port, the CLI will choose a random available port.\n\nBy 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`:\n\n```bash\n# Listen on all interfaces\ncopilot --headless --host 0.0.0.0 --port 4321\n```\n\n> \\[!WARNING]\n> 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.\n\n### Connecting the SDK to the external server\n\nOnce the CLI is running in server mode, configure your SDK client to connect to it using the \"cli url\" option:\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\nimport { CopilotClient, approveAll } from \"@github/copilot-sdk\";\n\nconst client = new CopilotClient({\n    cliUrl: \"localhost:4321\"\n});\n\n// Use the client normally\nconst session = await client.createSession({ onPermissionRequest: approveAll });\n// ...\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n```python\nfrom copilot import CopilotClient, RuntimeConnection\nfrom copilot.session import PermissionHandler\n\nclient = CopilotClient(connection=RuntimeConnection.for_uri(\"localhost:4321\"))\nawait client.start()\n\n# Use the client normally\nsession = await client.create_session(on_permission_request=PermissionHandler.approve_all)\n# ...\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n```golang\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\n\tcopilot \"github.com/github/copilot-sdk/go\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\n    client := copilot.NewClient(&copilot.ClientOptions{\n        Connection: copilot.URIConnection{URL: \"localhost:4321\"},\n    })\n\n\tif err := client.Start(ctx); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer client.Stop()\n\n\t// Use the client normally\n\t_, _ = client.CreateSession(ctx, &copilot.SessionConfig{\n\t\tOnPermissionRequest: copilot.PermissionHandler.ApproveAll,\n\t})\n}\n```\n\n```golang\nimport copilot \"github.com/github/copilot-sdk/go\"\n\nclient := copilot.NewClient(&copilot.ClientOptions{\n    Connection: copilot.URIConnection{URL: \"localhost:4321\"},\n})\n\nif err := client.Start(ctx); err != nil {\n    log.Fatal(err)\n}\ndefer client.Stop()\n\n// Use the client normally\nsession, err := client.CreateSession(ctx, &copilot.SessionConfig{\n    OnPermissionRequest: copilot.PermissionHandler.ApproveAll,\n})\n// ...\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"rust\" data-label=\"Rust\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Rust</div>\n\n```rust\nuse std::sync::Arc;\n\nuse github_copilot_sdk::handler::ApproveAllHandler;\nuse github_copilot_sdk::{Client, ClientOptions, SessionConfig, Transport};\n\nlet mut options = ClientOptions::default();\noptions.transport = Transport::External {\n    host: \"localhost\".to_string(),\n    port: 4321,\n};\nlet client = Client::start(options).await?;\n\n// Use the client normally\nlet session = client\n    .create_session(SessionConfig::default().with_permission_handler(Arc::new(ApproveAllHandler)))\n    .await?;\n// ...\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n```csharp\nusing GitHub.Copilot;\n\nusing var client = new CopilotClient(new CopilotClientOptions\n{\n    Connection = RuntimeConnection.ForUri(\"localhost:4321\"),\n});\n\n// Use the client normally\nawait using var session = await client.CreateSessionAsync(new()\n{\n    OnPermissionRequest = PermissionHandler.ApproveAll\n});\n// ...\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n```java\nimport com.github.copilot.CopilotClient;\nimport com.github.copilot.rpc.*;\n\nvar client = new CopilotClient(\n    new CopilotClientOptions().setCliUrl(\"localhost:4321\")\n);\nclient.start().get();\n\n// Use the client normally\nvar session = client.createSession(\n    new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)\n).get();\n// ...\n```\n\n</div>\n\n</div>\n\n**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.\n\n## Telemetry and observability\n\nThe Copilot SDK supports [OpenTelemetry](https://opentelemetry.io/) for distributed tracing. Provide a `telemetry` configuration to the client to enable trace export from the CLI process and automatic [W3C Trace Context](https://www.w3.org/TR/trace-context/) propagation between the SDK and CLI.\n\n### Enabling telemetry\n\nPass a `telemetry` (or `Telemetry`) config when creating the client. This is the opt-in—no separate \"enabled\" flag is needed.\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n<!-- docs-validate: skip -->\n\n```typescript\nimport { CopilotClient } from \"@github/copilot-sdk\";\n\nconst client = new CopilotClient({\n  telemetry: {\n    otlpEndpoint: \"http://localhost:4318\",\n  },\n});\n```\n\nOptional peer dependency: `@opentelemetry/api`\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n<!-- docs-validate: skip -->\n\n```python\nfrom copilot import CopilotClient, CopilotClientOptions\n\nclient = CopilotClient(CopilotClientOptions(\n    telemetry={\n        \"otlp_endpoint\": \"http://localhost:4318\",\n    },\n))\n```\n\nInstall with telemetry extras: `pip install copilot-sdk[telemetry]` (provides `opentelemetry-api`)\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n<!-- docs-validate: skip -->\n\n```golang\nclient, err := copilot.NewClient(copilot.ClientOptions{\n    Telemetry: &copilot.TelemetryConfig{\n        OTLPEndpoint: \"http://localhost:4318\",\n    },\n})\n```\n\nDependency: `go.opentelemetry.io/otel`\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"rust\" data-label=\"Rust\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Rust</div>\n\n<!-- docs-validate: skip -->\n\n```rust\nuse github_copilot_sdk::{Client, ClientOptions, OtelExporterType, TelemetryConfig};\n\nlet mut options = ClientOptions::default();\noptions.telemetry = Some(\n    TelemetryConfig::new()\n        .with_exporter_type(OtelExporterType::OtlpHttp)\n        .with_otlp_endpoint(\"http://localhost:4318\"),\n);\nlet client = Client::start(options).await?;\n```\n\nNo extra dependencies—the SDK injects telemetry environment variables for the spawned CLI process.\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n<!-- docs-validate: skip -->\n\n```csharp\nvar client = new CopilotClient(new CopilotClientOptions\n{\n    Telemetry = new TelemetryConfig\n    {\n        OtlpEndpoint = \"http://localhost:4318\",\n    },\n});\n```\n\nNo extra dependencies—uses built-in `System.Diagnostics.Activity`.\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n<!-- docs-validate: skip -->\n\n```java\nimport com.github.copilot.CopilotClient;\nimport com.github.copilot.rpc.*;\n\nvar client = new CopilotClient(new CopilotClientOptions()\n    .setTelemetry(new TelemetryConfig()\n        .setOtlpEndpoint(\"http://localhost:4318\")));\n```\n\nDependency: `io.opentelemetry:opentelemetry-api`\n\n</div>\n\n</div>\n\n### TelemetryConfig options\n\n| Option          | Node.js          | Python            | Go               | Rust              | Java             | .NET             | Description                                                            |\n| --------------- | ---------------- | ----------------- | ---------------- | ----------------- | ---------------- | ---------------- | ---------------------------------------------------------------------- |\n| OTLP endpoint   | `otlpEndpoint`   | `otlp_endpoint`   | `OTLPEndpoint`   | `otlp_endpoint`   | `otlpEndpoint`   | `OtlpEndpoint`   | OTLP HTTP endpoint URL                                                 |\n| OTLP protocol   | `otlpProtocol`   | `otlp_protocol`   | `OTLPProtocol`   | `otlp_protocol`   | `otlpProtocol`   | `OtlpProtocol`   | OTLP HTTP protocol for all signals: `\"http/json\"` or `\"http/protobuf\"` |\n| File path       | `filePath`       | `file_path`       | `FilePath`       | `file_path`       | `filePath`       | `FilePath`       | File path for JSON-lines trace output                                  |\n| Exporter type   | `exporterType`   | `exporter_type`   | `ExporterType`   | `exporter_type`   | `exporterType`   | `ExporterType`   | `\"otlp-http\"` or `\"file\"`                                              |\n| Source name     | `sourceName`     | `source_name`     | `SourceName`     | `source_name`     | `sourceName`     | `SourceName`     | Instrumentation scope name                                             |\n| Capture content | `captureContent` | `capture_content` | `CaptureContent` | `capture_content` | `captureContent` | `CaptureContent` | Whether to capture message content                                     |\n\nThe 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.\n\n### File export\n\nTo write traces to a local file instead of an OTLP endpoint:\n\n<!-- docs-validate: skip -->\n\n```typescript\nconst client = new CopilotClient({\n  telemetry: {\n    filePath: \"./traces.jsonl\",\n    exporterType: \"file\",\n  },\n});\n```\n\n### Trace context propagation\n\nTrace context is propagated automatically—no manual instrumentation is needed:\n\n* **SDK → CLI**: `traceparent` and `tracestate` headers from the current span/activity are included in `session.create`, `session.resume`, and `session.send` RPC calls.\n* **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.\n\n📖 **[OpenTelemetry instrumentation for Copilot SDK](/en/copilot/how-tos/copilot-sdk/observability/opentelemetry)**—TelemetryConfig options, trace context propagation, and per-language dependencies.\n\n## Learn more\n\n* [Authentication](/en/copilot/how-tos/copilot-sdk/auth/authenticate) - GitHub OAuth, environment variables, and BYOK\n* [BYOK (bring your own key)](/en/copilot/how-tos/copilot-sdk/auth/byok) - Use your own API keys from Azure AI Foundry, OpenAI, etc.\n* [Node.js SDK Reference](https://github.com/github/copilot-sdk/tree/main/nodejs/README.md)\n* [Python SDK Reference](https://github.com/github/copilot-sdk/tree/main/python/README.md)\n* [Go SDK Reference](https://github.com/github/copilot-sdk/tree/main/go/README.md)\n* [Rust SDK Reference](https://github.com/github/copilot-sdk/tree/main/rust/README.md)\n* [.NET SDK Reference](https://github.com/github/copilot-sdk/tree/main/dotnet/README.md)\n* [Java SDK Reference](https://github.com/github/copilot-sdk/tree/main/java/README.md)\n* [Using MCP servers with the GitHub Copilot SDK](/en/copilot/how-tos/copilot-sdk/features/mcp) - Integrate external tools via Model Context Protocol\n* [GitHub MCP Server Documentation](https://github.com/github/github-mcp-server)\n* [MCP Servers Directory](https://github.com/modelcontextprotocol/servers) - Explore more MCP servers\n* [OpenTelemetry instrumentation for Copilot SDK](/en/copilot/how-tos/copilot-sdk/observability/opentelemetry) - TelemetryConfig, trace context propagation, and per-language dependencies\n\n**You did it!** You've learned the core concepts of the GitHub Copilot SDK:\n\n* ✅ Creating a client and session\n* ✅ Sending messages and receiving responses\n* ✅ Streaming for real-time output\n* ✅ Defining custom tools that Copilot can call\n\nNow go build something amazing! 🚀"}