GitHub Copilot SDK
Embed the Copilot CLI's planning, tool-use, and file-editing engine directly into your application via JSON-RPC. Ships as parallel SDKs for Node.js, Python, Go, and .NET — each native-feeling, all driven by the same schema-generated protocol.
v0.3.0-preview.0 Protocol v3 Public Preview Multi-language
🎯 Project Philosophy
When you first open this repository, the most striking design choice is the absence of a shared runtime. There is no "core library" that all SDKs thunk into; no shared C++ module, no WebAssembly shim, no RPC proxy written in Rust. Instead, four SDKs reimplement the same behaviour from scratch — in TypeScript, Python, Go, and C# — and guarantee parity through three enforcement layers: a shared JSON Schema for the wire contract, a code generator that emits the RPC boilerplate, and a YAML-driven conformance test suite that diffs behaviour across all four.
This is a deliberate trade. A shared core would have been faster to build initially. But anyone who has used polyglot SDKs derived from a single code base knows the feeling — the Node SDK is a joy; the Python one is a thin wrapper over unmarshalled JSON; the Go one has a quirky camelCase API. By committing to per-language idiomaticity, every user of every SDK gets a first-class experience: Python users get async with CopilotClient(), .NET users get await using + CancellationToken, Go users get DefineTool[T,U] generics, TS users get discriminated-union SessionEvents. The cost — four parallel implementations — is managed by the contract-enforcement triad.
The second architectural commitment is treating the Copilot CLI binary as the server. Not an HTTP service, not a library. A sub-process. This feels old-school until you recognise how many problems it eliminates: process death is an OS-level signal (not a TCP timeout), clean-up is SIGKILL, authentication is inherited from the shell session, updates are a single binary swap, and no language's SDK has to reimplement the agent loop. The JSON-RPC framing (LSP-style Content-Length headers) is boring and well-understood; every SDK's "hardest file" is still under 400 lines.
The SDK prioritises correctness over performance and explicit over implicit. Every async entry point takes a cancellation token (or language equivalent). Capabilities are negotiated at session start — no try-call-catch-MethodNotFound patterns. Permission handlers get full tool call context (not just a boolean gate). The tool-use loop is observable via 40+ typed events, not a callback into a black box.
🏗️ Design Decisions
| Decision | Choice Made | Alternatives | Why This Approach |
|---|---|---|---|
| Polyglot architecture | Four independent SDKs, no shared runtime | Shared core via FFI; WASM; gRPC proxy | Every language gets idiomatic API; no transitive dependency chain; contract-driven parity rather than code-driven |
| Transport | JSON-RPC 2.0 over stdio or TCP, Content-Length framed | gRPC, HTTP, WebSocket, custom binary | Well-understood (LSP uses it), no extra deps per SDK, stdio gives OS-level back-pressure and lifecycle signals for free |
| Server topology | CLI spawned as sub-process | HTTP daemon; embedded library; system service | OS process semantics handle death/cleanup; auth inherited; no service management surface |
| Contract enforcement | JSON Schema → codegen → conformance tests | Hand-maintained docs; integration tests only | Three layers mean no silent drift: schema catches at compile time, tests catch at runtime |
| Protocol versioning | Single integer in sdk-protocol-version.json | Semver on wire; per-method versions; feature bitmap | Small matrix of compatible combinations; capability flags per session handle sub-version features |
| Extensibility | User-defined tools, commands, hooks, elicitation callbacks | Plugin loader; script embedding | Language-native callbacks keep the extension surface tiny; hooks are pure function interposition |
| Auth | Inherited from CLI login OR env vars OR BYOK provider keys | SDK-managed OAuth flow | The CLI already solves auth — one place to log in, one place to rotate tokens |
| Session FS | Optional SessionFsProvider interface | Always write to real disk | Lets users run sandboxed or in-memory session workspaces; test harnesses use it to avoid disk side-effects |
System Architecture
flowchart TD
subgraph User["Your Application"]
App[App code]
end
subgraph SDK_LANG["SDK layer - pick one"]
NODE[nodejs SDK
client.ts, session.ts]
PY[python SDK
client.py, session.py]
GO[go SDK
client.go, session.go]
NET[dotnet SDK
Client.cs, Session.cs]
end
subgraph CONTRACT["Shared contract"]
SCH[api.schema.json
session-events.schema.json]
VER[sdk-protocol-version.json]
CODEGEN[scripts codegen
emits rpc + session_events]
TESTS[test snapshots YAML
conformance fixtures]
end
subgraph CLI["Copilot CLI subprocess"]
AGT[Agent loop
plan -> tool -> respond]
TOOLS[Builtin tools
shell, file, web]
MCP[MCP servers
optional sub-processes]
end
App -->|language-native API| NODE
App -->|language-native API| PY
App -->|language-native API| GO
App -->|language-native API| NET
NODE -->|JSON-RPC / stdio or TCP| AGT
PY -->|JSON-RPC / stdio or TCP| AGT
GO -->|JSON-RPC / stdio or TCP| AGT
NET -->|JSON-RPC / stdio or TCP| AGT
SCH -->|read at build| CODEGEN
VER -->|embedded constant| NODE
VER -->|embedded constant| PY
VER -->|embedded constant| GO
VER -->|embedded constant| NET
CODEGEN -->|generated/ rpc.ts| NODE
CODEGEN -->|generated/ rpc.py| PY
CODEGEN -->|rpc generated_rpc.go| GO
CODEGEN -->|Generated/ Rpc.cs| NET
TESTS -.runs against.-> NODE
TESTS -.runs against.-> PY
TESTS -.runs against.-> GO
TESTS -.runs against.-> NET
AGT --> TOOLS
AGT --> MCP
Modules
Language SDKs
nodejs
Ships as @github/copilot-sdk on npm. Uses vscode-jsonrpc for transport; supports Zod or plain JSON Schema for tool params. Bundles the Copilot CLI as a transitive npm dependency.
python
Ships as github-copilot-sdk on PyPI. Threaded JSON-RPC bridge sidesteps asyncio subprocess pitfalls on Windows. async with lifecycle; TypedDict payloads; stdlib-only.
go
Uses Go 1.18+ generics for DefineTool[T,U]. Custom internal/jsonrpc2 transport; flock-guarded CLI extraction; build-tag split files for Windows/Unix syscalls.
dotnet
NuGet package GitHub.Copilot.SDK. Source-generated converters for polymorphic events; IAsyncDisposable lifecycle; Channel<T> for event streaming; custom MillisecondsTimeSpanConverter.
Shared infrastructure
scripts
Reads JSON Schemas shipped with the CLI and emits RPC bindings into each SDK's generated/ folder. Four language-specific emitters share utils.ts for schema normalisation.
test
YAML scenarios run through every SDK and diff against stored snapshots. Organised by feature (permissions/, hooks_extended/, session_fs/, ...), not by SDK — enforces behavioural parity.
🔗 Key Data Flows
Flow A — Single-prompt conversation
sequenceDiagram
participant App as Your App
participant SDK as SDK Client
participant CLI as Copilot CLI subprocess
participant LLM as LLM provider
App->>SDK: new CopilotClient / createSession
SDK->>CLI: spawn / TCP connect
SDK->>CLI: JSON-RPC getStatus
CLI-->>SDK: protocolVersion + capabilities
Note over SDK,CLI: WHY: negotiate features before first turn
SDK->>CLI: JSON-RPC session.create
CLI-->>SDK: sessionId + session.capabilities
App->>SDK: session.sendAndWait(prompt)
SDK->>SDK: register idle listener (race fix)
SDK->>CLI: JSON-RPC session.send
CLI->>LLM: prompt + tools + system message
LLM-->>CLI: assistant response
CLI-->>SDK: event: assistant.message (streamed)
CLI-->>SDK: event: session.idle
SDK-->>App: resolved Promise / Task / channel
App->>SDK: client.stop() / dispose
SDK->>CLI: JSON-RPC shutdown
Flow B — Tool invocation round-trip
sequenceDiagram
participant LLM as LLM
participant CLI as Copilot CLI
participant SDK as SDK (server-to-client RPC)
participant Hook as preToolUse hook
participant Perm as Permission handler
participant Tool as User tool handler
LLM->>CLI: decide: call get_weather(Seattle)
CLI->>SDK: session/toolInvoke RPC
Note over CLI,SDK: WHY: CLI calls back into SDK because tool lives in user code
SDK->>Hook: pre-tool-use(toolName, args)
Hook-->>SDK: modified args OR deny
SDK->>Perm: request approval?
Note over SDK,Perm: WHY: user gates destructive actions before execution
Perm-->>SDK: approved
SDK->>Tool: handler(args)
Tool-->>SDK: "Cloudy, 62F"
SDK-->>CLI: RPC reply with result
CLI->>LLM: tool result appended to context
LLM-->>CLI: final assistant message
CLI-->>SDK: event: assistant.message + session.idle
Flow C — Codegen pipeline (build time)
flowchart LR SCH[api.schema.json
session-events.schema.json
from @github/copilot/schemas] UTILS[scripts/codegen/utils.ts
postProcessSchema] TSE[typescript.ts] PYE[python.ts] GOE[go.ts] CSE[csharp.ts] NODE[nodejs/src/generated/rpc.ts
nodejs/src/generated/session-events.ts] PY[python/copilot/generated/rpc.py
session_events.py] GO[go/rpc/generated_rpc.go] NET[dotnet/src/Generated/Rpc.cs
SessionEvents.cs] SCH --> UTILS UTILS --> TSE --> NODE UTILS --> PYE --> PY UTILS --> GOE --> GO UTILS --> CSE --> NET
Navigation Guide
| Level | What You'll Find | When to Use |
|---|---|---|
| L0 — Project Hub (this page) | Philosophy, cross-SDK design, system flows | New to the repo |
| L1 — Module Overview | Per-SDK architecture, components, design rationale | Using or working on a specific SDK |
| Focus pages | Deep-dive: JSON-RPC framing, protocol negotiation, codegen pipeline | Understanding cross-cutting mechanisms |
How SDK & CLI Connect
Every SDK speaks to the CLI through the same two-way JSON-RPC channel. Understanding the boundary is the single most important concept:
sequenceDiagram
participant App as User App
participant SDK as SDK client-side handlers
participant RPC as JSON-RPC connection
Content-Length framed
participant CLI as CLI server-side handlers
App->>SDK: client.createSession()
SDK->>RPC: request: session.create {config}
RPC->>CLI: bytes
CLI->>CLI: spawn internal session
CLI-->>RPC: response {sessionId, capabilities}
Note over RPC: SDK -> CLI: user-intent calls
App->>SDK: session.send("hello")
SDK->>RPC: request: session.send
RPC->>CLI: bytes
CLI->>RPC: notification: assistant.message
RPC->>SDK: dispatched to SessionEvent listeners
SDK-->>App: event callback fires
CLI->>RPC: request: session/toolInvoke {tool,args}
Note over RPC: CLI -> SDK: tool/permission/FS calls (bidirectional!)
RPC->>SDK: dispatched to registered tool handler
SDK->>App: user's tool code runs
App-->>SDK: tool result
SDK->>RPC: response {result}
RPC->>CLI: bytes
CLI->>RPC: notification: session.idle
RPC->>SDK: resolves sendAndWait promise
Key insight. The JSON-RPC channel is bidirectional. The SDK makes calls likesession.createandsession.send; the CLI calls back for tool execution, permissions, FS operations, and elicitation. Every SDK must register both client-to-server call shims AND server-to-client handler maps on connect. This is why every SDK has aregisterClientSessionApiHandlers-equivalent function.
Directory Map
Project source tree
copilot-sdk/
├── nodejs/ ← TypeScript SDK (reference impl)
│ ├── src/ ← client.ts, session.ts, types.ts, sessionFsProvider.ts
│ ├── src/generated/ ← codegen output (rpc.ts, session-events.ts)
│ ├── samples/ examples/ ← chat demo, basic-example
│ ├── scripts/ ← update-protocol-version
│ └── test/e2e/ ← end-to-end tests
├── python/ ← Async Python SDK
│ ├── copilot/ ← client.py, session.py, _jsonrpc.py, tools.py
│ ├── copilot/generated/ ← codegen output
│ ├── samples/ e2e/ ← chat demo + harness
│ └── scripts/
├── go/ ← Go SDK
│ ├── client.go session.go ← main entry points
│ ├── definetool.go ← generic tool wrapper
│ ├── internal/jsonrpc2/ ← custom JSON-RPC impl
│ ├── internal/embeddedcli/ ← bundled CLI extraction
│ ├── internal/flock/ ← cross-platform file locking
│ ├── rpc/ ← codegen output
│ ├── cmd/bundler/ ← release-time bundling tool
│ └── samples/
├── dotnet/ ← .NET SDK
│ ├── src/ ← Client.cs, Session.cs, Types.cs
│ ├── src/Generated/ ← codegen output
│ ├── samples/ test/
├── scripts/ ← codegen pipeline
│ ├── codegen/ ← typescript.ts, python.ts, go.ts, csharp.ts, utils.ts
│ ├── corrections/ ← hand-patched regions
│ └── docs-validation/
├── test/ ← cross-SDK conformance
│ ├── snapshots/ ← YAML fixtures per feature
│ └── harness/ scenarios/
├── docs/ ← narrative docs (features, hooks, auth)
├── assets/ ← images for README
├── justfile ← dev task runner
├── sdk-protocol-version.json ← single-source protocol integer
├── CHANGELOG.md ← per-release feature log
└── README.md
Build & Tech Stack
Languages
TypeScript, Python, Go, C# + YAML fixtures
Build tools
npm (TS), hatch (Py), go build, dotnet + NuGet, just for cross-repo tasks
Codegen
json-schema-to-typescript, quicktype
Transport
vscode-jsonrpc (TS), hand-rolled elsewhere
Auth
OAuth via CLI, env tokens, BYOK (OpenAI / Anthropic / Azure)
Release cadence
Public preview — see CHANGELOG.md