Contribution & Extension Guide
Comprehensive reference for contributors, extension authors, and AI agents working with the copilot-sdk mono-repo.
1. Getting Started as a Contributor
1.1 Repository Architecture
flowchart TD
A["Your Application"] -->|"JSON-RPC"| B["SDK Client"]
B -->|"JSON-RPC"| C["Copilot CLI\n(server mode)"]
copilot-sdk/
nodejs/ # Node.js / TypeScript SDK
python/ # Python SDK
go/ # Go SDK
dotnet/ # .NET SDK
scripts/codegen/ # Cross-language type generation
test/ # Shared test infrastructure
harness/ # Replaying CAPI proxy for E2E
snapshots/ # YAML snapshot exchanges
scenarios/ # E2E scenario tests (all languages)
docs/ # Cross-language documentation
justfile # Task runner recipes
1.2 Prerequisites
The just task runner provides unified commands across all SDKs.
flowchart LR
subgraph TS["Node.js / TypeScript"]
TS1["Node.js v18+"]
TS2["cd nodejs && npm ci"]
end
subgraph PY["Python"]
PY1["Python 3.8+ and uv"]
PY2["cd python && uv pip install -e .[dev]"]
end
subgraph GO["Go"]
GO1["Go 1.24+ and golangci-lint"]
GO2["cd go && go mod download"]
end
subgraph CS["C# / .NET"]
CS1[".NET 8.0+ and Node.js v18+"]
CS2["cd dotnet && dotnet restore"]
end
1.3 One-Command Setup
just install
This installs all dependencies for all four SDKs plus the test harness.
1.4 Dev Container
A devcontainer at .devcontainer/devcontainer.json provides a fully-configured environment with Copilot CLI, GitHub CLI, Go, Node.js, uv, just, and .NET.
1.5 VS Code Settings
The repo provides VS Code settings with Prettier (default formatter), Ruff (Python), Go extension, format on save, trailing whitespace trimming, and 4-space tabs.
1.6 Code Ownership
The entire repository is owned by @github/copilot-sdk (.github/CODEOWNERS:1).
2. Development Workflow
2.1 The just Task Runner
| Command | Description |
|---|---|
just test | Run tests for all 4 languages |
just lint | Lint all 4 languages |
just format | Format all 4 languages |
just install | Install all dependencies |
just playground | Start the SDK playground |
just validate-docs | Validate documentation code examples |
just scenario-build | Build all E2E scenario samples |
just scenario-verify | Run full E2E scenario verification |
2.2 Per-Language Commands
# Format
cd nodejs && npm run format
# Lint
cd nodejs && npm run format:check && npm run lint && npm run typecheck
# Test (Vitest)
cd nodejs && npm test
# Format
cd python && uv run ruff format .
# Lint
cd python && uv run ruff check . && uv run ty check copilot
# Test
cd python && uv run pytest
# Format (excludes generated code)
cd go && find . -name "*.go" -not -path "*/generated/*" -exec gofmt -w {} +
# Lint
cd go && golangci-lint run ./...
# Test
cd go && go test ./...
# Format
cd dotnet && dotnet format src/GitHub.Copilot.SDK.csproj
# Lint
cd dotnet && dotnet format src/GitHub.Copilot.SDK.csproj --verify-no-changes
# Test
cd dotnet && dotnet test test/GitHub.Copilot.SDK.Test.csproj
2.3 Submitting a Pull Request
- Fork and clone the repository
- Install dependencies for the SDK(s) you are modifying
- Make sure tests pass on your machine
- Make sure linting passes
- Create a new branch:
git checkout -b my-branch-name - Make your change, add tests, verify tests/linter pass
- Push to your fork and submit a pull request
- Wait for review
2.4 What Contributions Are Welcome
- Fixing bugs in the existing feature set
- Making the SDKs more idiomatic for each language
- Improving documentation
- New features (post an issue or start a discussion first)
New language SDKs are not accepted in the repo — maintainers encourage external community projects instead.
3. Code Generation Workflow
3.1 Overview
Type generation is centralized in scripts/codegen/. The system reads JSON schemas from the @github/copilot package and generates typed code for all four languages.
3.2 Code Generation Scripts
| Script | Output | Command |
|---|---|---|
| typescript.ts | nodejs/src/generated/ | npm run generate:ts |
| python.ts | python generated types | npm run generate:python |
| go.ts | go generated types | npm run generate:go |
| csharp.ts | dotnet generated types | npm run generate:csharp |
cd scripts/codegen && npm run generate
3.3 Generation Pipeline
flowchart TD
A["JSON-RPC Schema\n(rpc-schema.json)"] --> B["Code Generator\n(just codegen)"]
B --> C["TypeScript\ngenerated types"]
B --> D["Python\ngenerated types"]
B --> E["Go\ngenerated types"]
B --> F["C#\ngenerated types"]
3.5 Generated File Locations
| Language | Session Events | RPC Types |
|---|---|---|
| TS | nodejs/src/generated/session-events.ts | nodejs/src/generated/rpc.ts |
| Python | python/copilot/generated/ | python/copilot/generated/ |
| Go | go/generated_session_events.go | go/rpc/ |
| C# | dotnet/src/ (generated) | dotnet/src/ (generated) |
Generated files include AUTO-GENERATED FILE - DO NOT EDIT banners.
4. Adding a New RPC Method
flowchart TD
A["Update rpc-schema.json"] --> B["Run just codegen"]
B --> C["Review generated diffs"]
C --> D["Implement handler in each SDK"]
D --> E["Add unit tests per SDK"]
E --> F["Add E2E scenario test"]
F --> G["Update CLI if needed"]
5. Adding a New Feature
5.1 SDK Source Code Locations
| Language | Source | Unit Tests | E2E Tests |
|---|---|---|---|
| TS | nodejs/src/ | nodejs/test/ | nodejs/test/e2e/ |
| Python | python/copilot/ | python/test_*.py | python/e2e/ |
| Go | go/ | go/*_test.go | go/ (inline) |
| C# | dotnet/src/ | dotnet/test/ | dotnet/test/ |
5.3 Feature Implementation Checklist
- Design the API surface in each language idiomatically
- Implement in all 4 SDKs — changes must be synchronized
- Write unit tests for each SDK
- Write E2E scenario tests
- Add snapshot data if new CAPI exchanges are needed
- Write documentation in
docs/features/ - Validate docs with
just validate-docs
6. Writing Tests
6.1 Unit Tests
flowchart LR
subgraph TS["Node.js - Vitest"]
TS1["nodejs/test/"]
TS2["npm test"]
end
subgraph PY["Python - pytest"]
PY1["python/test_*.py"]
PY2["uv run pytest"]
end
subgraph GO["Go"]
GO1["go/*_test.go"]
GO2["go test ./..."]
end
subgraph CS[".NET"]
CS1["dotnet/test/"]
CS2["dotnet test"]
end
6.2 E2E Tests and the Replay Proxy
E2E tests run against a replaying CAPI proxy located in test/harness/:
- server.ts — The replay proxy server
- replayingCapiProxy.ts — Replays recorded CAPI exchanges
- capturingHttpProxy.ts — Captures live exchanges for recording
- test-mcp-server.mjs — Mock MCP server for tests
Snapshot data in test/snapshots/: YAML files with recorded request/response exchanges, organized by feature area.
6.3 Scenario Tests
scenarios/
auth/ # Authentication flows
bundling/ # Deployment architectures
callbacks/ # Lifecycle hooks, permissions
modes/ # Preset modes (CLI, filesystem)
prompts/ # Prompt configuration
sessions/ # Session management
tools/ # Tool capabilities
transport/ # Wire protocols
6.4 Adding a New Test Scenario
- Create subdirectory under the appropriate category in
test/scenarios/ - Add implementations in
typescript/,python/,go/,csharp/ - Add a
verify.shscript - Update snapshot YAML files if needed
- Run
just scenario-buildto verify
7. Creating Custom Agents
Custom agents have their own system prompt, tool restrictions, and optional MCP servers. The runtime automatically delegates based on intent matching.
7.1 Defining Custom Agents
const session = await client.createSession({
model: "gpt-4.1",
customAgents: [
{
name: "researcher",
displayName: "Research Agent",
description: "Explores codebases using read-only tools",
tools: ["grep", "glob", "view"],
prompt: "You are a research assistant.",
},
{
name: "editor",
displayName: "Editor Agent",
tools: ["view", "edit", "bash"],
prompt: "You are a code editor.",
},
],
onPermissionRequest: async () => ({ kind: "approved" }),
});
session = await client.create_session({
"model": "gpt-4.1",
"custom_agents": [
{
"name": "researcher",
"display_name": "Research Agent",
"tools": ["grep", "glob", "view"],
"prompt": "You are a research assistant.",
},
],
"on_permission_request": lambda req, inv: PermissionRequestResult(kind="approved"),
})
session, _ := client.CreateSession(ctx, &copilot.SessionConfig{
Model: "gpt-4.1",
CustomAgents: []copilot.CustomAgentConfig{
{
Name: "researcher",
DisplayName: "Research Agent",
Tools: []string{"grep", "glob", "view"},
Prompt: "You are a research assistant.",
},
},
})
7.2 Configuration Reference
| Property | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique identifier |
displayName | string | No | Human-readable name |
description | string | No | Helps runtime select the agent |
tools | string[] | null | No | Tool names. null = all |
prompt | string | Yes | System prompt |
mcpServers | object | No | Per-agent MCP configs |
infer | boolean | No | Auto-select (default: true) |
7.4 Sub-Agent Events
| Event | Emitted When |
|---|---|
subagent.selected | Runtime selects an agent |
subagent.started | Sub-agent begins execution |
subagent.completed | Sub-agent finishes |
subagent.failed | Sub-agent encounters an error |
subagent.deselected | Runtime switches away |
8. Extending with Skills
Skills are reusable prompt modules loaded from directories containing SKILL.md files.
8.2 SKILL.md Format
---
name: code-review
description: Specialized code review capabilities
---
# Code Review Guidelines
When reviewing code, always check for:
1. Security vulnerabilities
2. Performance issues
3. Code style
4. Test coverage
8.3 Loading Skills
const session = await client.createSession({
skillDirectories: ["./skills/code-review", "./skills/documentation"],
});
session = await client.create_session({
"skill_directories": ["./skills/code-review", "./skills/documentation"],
})
8.5 Configuration Fields Per Language
| Language | Load Field | Disable Field |
|---|---|---|
| TS | skillDirectories | disabledSkills |
| Python | skill_directories | disabled_skills |
| Go | SkillDirectories | DisabledSkills |
| C# | SkillDirectories | DisabledSkills |
9. Extending with MCP Servers
MCP (Model Context Protocol) servers provide pre-built tools that Copilot can invoke during conversations.
9.1 Server Types
| Type | Description | Use Case |
|---|---|---|
| Local/Stdio | Subprocess via stdin/stdout | Local tools, file access |
| HTTP/SSE | Remote server via HTTP | Shared services, cloud tools |
9.2 Configuration
const session = await client.createSession({
mcpServers: {
"my-local-server": {
type: "local",
command: "node",
args: ["./mcp-server.js"],
env: { DEBUG: "true" },
tools: ["*"],
timeout: 30000,
},
},
});
session = await client.create_session({
"mcp_servers": {
"my-local-server": {
"type": "local",
"command": "python",
"args": ["./mcp_server.py"],
"tools": ["*"],
},
},
})
session, _ := client.CreateSession(ctx, &copilot.SessionConfig{
MCPServers: map[string]copilot.MCPServerConfig{
"my-local-server": {
"type": "local",
"command": "node",
"args": []string{"./mcp-server.js"},
"tools": []string{"*"},
},
},
})
9.3 Local Server Options
| Property | Type | Required | Description |
|---|---|---|---|
type | "local" | No | Server type |
command | string | Yes | Command to execute |
args | string[] | Yes | Command arguments |
env | object | No | Environment variables |
tools | string[] | No | Tools to enable |
timeout | number | No | Timeout in ms |
10. Extending with Hooks
Hooks let you plug custom logic into every stage of a Copilot session.
10.1 Available Hooks
| Hook | When It Fires | What You Can Do |
|---|---|---|
onSessionStart | Session begins | Inject context, load preferences |
onUserPromptSubmitted | User sends message | Rewrite prompts, add context |
onPreToolUse | Before tool executes | Allow / deny / modify the call |
onPostToolUse | After tool returns | Transform results, redact secrets |
onSessionEnd | Session ends | Clean up, record metrics |
onErrorOccurred | Error is raised | Custom logging, retry logic |
10.3 Hook Input/Output Reference
- onUserPromptSubmitted: Input:
{ prompt, timestamp, cwd }. Output:{ modifiedPrompt?, additionalContext? } - onPreToolUse: Input:
{ toolName, toolArgs }. Output:{ permissionDecision?, modifiedArgs? } - onPostToolUse: Input:
{ toolName, toolResult }. Output:{ modifiedResult? } - onSessionStart: Input:
{ source, initialPrompt? }. Output:{ additionalContext? } - onSessionEnd: Input:
{ reason, finalMessage? }. Output:{ sessionSummary? }
10.5 Best Practices
- Keep hooks fast — they run inline and delay the conversation
- Return null when unchanged — tells SDK to use defaults
- Be explicit with permission decisions
- Don't swallow critical errors — always log or alert
- Use additionalContext over modifiedPrompt — preserves user intent
- Scope state by session ID
11. Fork Guide
- Fork at
github.com/github/copilot-sdk/fork - Clone:
git clone https://github.com/YOUR-USERNAME/copilot-sdk.git - Install:
just install - Verify:
just test
11.3 Keeping Your Fork Updated
git remote add upstream https://github.com/github/copilot-sdk.git
git fetch upstream
git merge upstream/main
cd scripts/codegen && npm run generate # if schema changed
just test
11.4 Building Extensions (Without Forking)
For most customization, you do not need to fork. The SDK provides extension points:
- Custom Tools:
defineTool()/@define_tool/DefineTool()/AIFunctionFactory.Create() - Custom Agents:
customAgentsin session config - Skills:
skillDirectoriesfor prompt modules - MCP Servers:
mcpServersfor external tools - Hooks:
hooksfor lifecycle interception - CLI Extensions:
.github/extensions/for.mjsextensions
12. AI Agent Guide
How an AI coding agent should approach modifying this codebase.
12.1 Files to Read First
- README.md — Architecture and quick start
- Language entry points: nodejs/src/client.ts, python/README.md, go/README.md
- Test harness: test/harness/*
- Schemas and codegen: scripts/codegen/
- Session snapshots: test/snapshots/
12.2 Where to Add New Code
| What | Location |
|---|---|
| SDK source | nodejs/src, python/copilot, go, dotnet/src |
| Unit tests | nodejs/test, python/*, go/*, dotnet/test |
| E2E tests | */e2e/ folders using shared replay proxy |
| Generated types | Run cd scripts/codegen && npm run generate |
12.5 Cross-Language Change Checklist
- Check if change affects all four SDKs
- Run code generation if schema changed
- Add tests in all affected languages
- Update snapshot data if new CAPI exchanges
- Run
just lintbefore committing - Run
just testto verify - Validate docs:
just validate-docs - Never edit files with
AUTO-GENERATEDbanners
12.8 Extension Authoring
- Scaffold:
extensions_manage({ operation: "scaffold", name: "my-extension" }) - Edit: Modify
extension.mjs(must be ES module, must calljoinSession()) - Reload:
extensions_reload({}) - Verify:
extensions_manage({ operation: "list" })
stdoutis reserved for JSON-RPC — usesession.log()instead- Tool name collisions across extensions are fatal
- Do not call
session.send()fromonUserPromptSubmitted - Extensions reload on
/clear— in-memory state is lost - Only
.mjsis supported (not TypeScript)
Appendix: Quick Reference Card
flowchart LR
A["<b>just install</b><br/>Install Everything"] ~~~ B["<b>npm run generate</b><br/>Regenerate All Types"] ~~~ C["<b>just format && lint && test</b><br/>Full CI Check"]
Key File Locations
| Purpose | Path |
|---|---|
| Root README | README.md |
| Contributing guide | CONTRIBUTING.md |
| Task runner | justfile |
| AI agent instructions | .github/copilot-instructions.md |
| Node.js SDK source | nodejs/src/ |
| Python SDK source | python/copilot/ |
| Go SDK source | go/ |
| .NET SDK source | dotnet/src/ |
| Code generators | scripts/codegen/ |
| Test harness | test/harness/ |
| E2E snapshots | test/snapshots/ |
| E2E scenarios | test/scenarios/ |
| Feature docs | docs/features/ |
| Protocol version | sdk-protocol-version.json |