All notable changes to the Copilot SDK are documented in this file.
This changelog is automatically generated by an AI agent when stable releases are published. See GitHub Releases for the full list.
v0.2.1 (2026-04-03)
Register slash commands that CLI users can invoke and drive interactive input dialogs from any SDK language. This feature was previously Node.js-only; it now ships in Python, Go, and .NET as well. (#906, #908, #960)
const session = await client.createSession({
onPermissionRequest: approveAll,
commands: [{
name: "summarize",
description: "Summarize the conversation",
handler: async (context) => { /* ... */ },
}],
onElicitationRequest: async (context) => {
if (context.type === "confirm") return { action: "confirm" };
},
});
// Drive dialogs from the session
const confirmed = await session.ui.confirm({ message: "Proceed?" });
const choice = await session.ui.select({ message: "Pick one", options: ["A", "B"] });var session = await client.CreateSessionAsync(new SessionConfig {
OnPermissionRequest = PermissionHandler.ApproveAll,
Commands = [
new CommandDefinition {
Name = "summarize",
Description = "Summarize the conversation",
Handler = async (context) => { /* ... */ },
}
],
});
// Drive dialogs from the session
var confirmed = await session.Ui.ConfirmAsync(new ConfirmOptions { Message = "Proceed?" });
⚠️ Breaking change (Node.js): TheonElicitationRequesthandler signature changed from two arguments (request, invocation) to a singleElicitationContextthat combines both. Update callers to usecontext.sessionIdandcontext.messagedirectly.
Efficiently fetch metadata for a single session by ID without listing all sessions. Returns undefined/null (not an error) when the session is not found. (#899)
- TypeScript:
const meta = await client.getSessionMetadata(sessionId); - C#:
var meta = await client.GetSessionMetadataAsync(sessionId); - Python:
meta = await client.get_session_metadata(session_id) - Go:
meta, err := client.GetSessionMetadata(ctx, sessionID)
Supply a custom sessionFs adapter in Node SDK session config to redirect the runtime's per-session storage (event log, large output files) to any backing store — useful for serverless deployments or custom persistence layers. (#917)
- bugfix: structured tool results (with
toolTelemetry,resultType, etc.) now sent via RPC as objects instead of being stringified, preserving metadata for Node, Go, and Python SDKs (#970) - feature: [Python]
CopilotClientandCopilotSessionnow supportasync withfor automatic resource cleanup (#475) - improvement: [Python]
copilot.typesmodule removed; import types directly fromcopilot(#871) - improvement: [Python]
workspace_pathnow accepts anyos.PathLikeandsession.workspace_pathreturns apathlib.Path(#901) - improvement: [Go] simplified
rpcpackage API: renamed structs drop the redundantRpcinfix (e.g.ModelRpcApi→ModelApi) (#905) - fix: [Go]
Session.SetModelnow takes a pointer for optional options instead of a variadic argument (#904)
- @Sumanth007 made their first contribution in #475
- @jongalloway made their first contribution in #957
- @Morabbin made their first contribution in #970
- @schneidafunk made their first contribution in #998
v0.2.0 (2026-03-20)
This is a big update with a broad round of API refinements, new capabilities, and cross-SDK consistency improvements that have shipped incrementally through preview releases since v0.1.32.
A new "customize" mode for systemMessage lets you surgically edit individual sections of the Copilot system prompt — without replacing the entire thing. Ten sections are configurable: identity, tone, tool_efficiency, environment_context, code_change_rules, guidelines, safety, tool_instructions, custom_instructions, and last_instructions.
Each section supports four static actions (replace, remove, append, prepend) and a transform callback that receives the current rendered content and returns modified text — useful for regex mutations, conditional edits, or logging what the prompt contains. (#816)
const session = await client.createSession({
onPermissionRequest: approveAll,
systemMessage: {
mode: "customize",
sections: {
identity: {
action: (current) => current.replace("GitHub Copilot", "Acme Assistant"),
},
tone: { action: "replace", content: "Be concise and professional." },
code_change_rules: { action: "remove" },
},
},
});var session = await client.CreateSessionAsync(new SessionConfig {
OnPermissionRequest = PermissionHandler.ApproveAll,
SystemMessage = new SystemMessageConfig {
Mode = SystemMessageMode.Customize,
Sections = new Dictionary<string, SectionOverride> {
["identity"] = new() {
Transform = current => Task.FromResult(current.Replace("GitHub Copilot", "Acme Assistant")),
},
["tone"] = new() { Action = SectionOverrideAction.Replace, Content = "Be concise and professional." },
["code_change_rules"] = new() { Action = SectionOverrideAction.Remove },
},
},
});All four SDK languages now support distributed tracing with the Copilot CLI. Set telemetry in your client options to configure an OTLP exporter; W3C trace context is automatically propagated on session.create, session.resume, and session.send, and restored in tool handlers so tool execution is linked to the originating trace. (#785)
const client = new CopilotClient({
telemetry: {
otlpEndpoint: "http://localhost:4318",
sourceName: "my-app",
},
});var client = new CopilotClient(new CopilotClientOptions {
Telemetry = new TelemetryConfig {
OtlpEndpoint = "http://localhost:4318",
SourceName = "my-app",
},
});- Python:
CopilotClient(SubprocessConfig(telemetry={"otlp_endpoint": "http://localhost:4318", "source_name": "my-app"})) - Go:
copilot.NewClient(&copilot.ClientOptions{Telemetry: &copilot.TelemetryConfig{OTLPEndpoint: "http://localhost:4318", SourceName: "my-app"}})
A new blob attachment type lets you send images or other binary content directly to a session without writing to disk — useful when data is already in memory (screenshots, API responses, generated images). (#731)
await session.send({
prompt: "What's in this image?",
attachments: [{ type: "blob", data: base64Str, mimeType: "image/png" }],
});await session.SendAsync(new MessageOptions {
Prompt = "What's in this image?",
Attachments = [new UserMessageDataAttachmentsItemBlob { Data = base64Str, MimeType = "image/png" }],
});You can now specify which custom agent should be active when a session starts, eliminating the need for a separate session.rpc.agent.select() call. (#722)
const session = await client.createSession({
customAgents: [
{ name: "researcher", prompt: "You are a research assistant." },
{ name: "editor", prompt: "You are a code editor." },
],
agent: "researcher",
onPermissionRequest: approveAll,
});var session = await client.CreateSessionAsync(new SessionConfig {
CustomAgents = [
new CustomAgentConfig { Name = "researcher", Prompt = "You are a research assistant." },
new CustomAgentConfig { Name = "editor", Prompt = "You are a code editor." },
],
Agent = "researcher",
OnPermissionRequest = PermissionHandler.ApproveAll,
});skipPermissionon tool definitions — Tools can now be registered withskipPermission: trueto bypass the confirmation prompt for low-risk operations like read-only queries. Available in all four SDKs. (#808)reasoningEffortwhen switching models — All SDKs now accept an optionalreasoningEffortparameter insetModel()for models that support it. (#712)- Custom model listing for BYOK — Applications using bring-your-own-key providers can supply
onListModelsin client options to overrideclient.listModels()with their own model list. (#730) no-resultpermission outcome — Permission handlers can now return"no-result"so extensions can attach to sessions without actively answering permission requests. (#802)SessionConfig.onEventcatch-all — A newonEventhandler on session config is registered before the RPC is issued, guaranteeing that early events likesession.startare never dropped. (#664)- Node.js CJS compatibility — The Node.js SDK now ships both ESM and CJS builds, fixing crashes in VS Code extensions and other tools bundled with esbuild's
format: "cjs". No changes needed in consumer code. (#546) - Experimental API annotations — APIs marked experimental in the schema (agent, fleet, compaction groups) are now annotated in all four SDKs:
[Experimental]in C#,/** @experimental */in TypeScript, and comments in Python and Go. (#875) - System notifications and session log APIs — Updated to match the latest CLI runtime, adding
system.notificationevents and a session log RPC API. (#737)
- [.NET, Go] Serialize event dispatch so handlers are invoked in registration order with no concurrent calls (#791)
- [Go] Detach CLI process lifespan from the context passed to
Client.Startso cancellation no longer kills the child process (#689) - [Go] Stop RPC client logging expected EOF errors (#609)
- [.NET] Emit XML doc comments from schema descriptions in generated RPC code (#724)
- [.NET] Use lazy property initialization in generated RPC classes (#725)
- [.NET] Add
DebuggerDisplayattribute toSessionEventfor easier debugging (#726) - [.NET] Optional RPC params are now represented as optional method params for forward-compatible generated code (#733)
- [.NET] Replace
Task.WhenAny+Task.Delaytimeout pattern with.WaitAsync(TimeSpan)(#805) - [.NET] Add NuGet package icon (#688)
- [Node] Don't resolve
cliPathwhencliUrlis already set (#787)
We've added low-level RPC methods to control a lot more of what's going on in the session. These are emerging APIs that don't yet have friendly wrappers, and some may be flagged as experimental or subject to change.
session.rpc.skills.list(),.enable(name),.disable(name),.reload()session.rpc.mcp.list(),.enable(name),.disable(name),.reload()session.rpc.extensions.list(),.enable(name),.disable(name),.reload()session.rpc.plugins.list()session.rpc.ui.elicitation(...)— structured user inputsession.rpc.shell.exec(command),.kill(pid)session.log(message, level, ephemeral)
In an forthcoming update, we'll add friendlier wrappers for these.
- [.NET] Fix
SessionEvent.ToJson()failing for events withJsonElement-backed payloads (assistant.message,tool.execution_start, etc.) (#868) - [.NET] Add fallback
TypeInfoResolverforStreamJsonRpc.RequestIdto fix NativeAOT compatibility (#783) - [.NET] Fix codegen for discriminated unions nested within other types (#736)
- [.NET] Handle unknown session event types gracefully instead of throwing (#881)
autoRestartremoved — TheautoRestartoption has been deprecated across all SDKs (it was never fully implemented). The property still exists but has no effect and will be removed in a future release. Remove any references toautoRestartfrom your client options. (#803)
The Python SDK received a significant API surface overhaul in this release, replacing loosely-typed TypedDict config objects with proper keyword arguments and dataclasses. These changes improve IDE autocompletion, type safety, and readability.
-
CopilotClientconstructor redesigned — TheCopilotClientOptionsTypedDict has been replaced by two typed config dataclasses. (#793)# Before (v0.1.x) client = CopilotClient({"cli_url": "localhost:3000"}) client = CopilotClient({"cli_path": "/usr/bin/copilot", "log_level": "debug"}) # After (v0.2.0) client = CopilotClient(ExternalServerConfig(url="localhost:3000")) client = CopilotClient(SubprocessConfig(cli_path="/usr/bin/copilot", log_level="debug"))
-
create_session()andresume_session()now take keyword arguments instead of aSessionConfig/ResumeSessionConfigTypedDict.on_permission_requestis now a required keyword argument. (#587)# Before session = await client.create_session({ "on_permission_request": PermissionHandler.approve_all, "model": "gpt-4.1", }) # After session = await client.create_session( on_permission_request=PermissionHandler.approve_all, model="gpt-4.1", )
-
send()andsend_and_wait()take a positionalpromptstring instead of aMessageOptionsTypedDict. Attachments and mode are now keyword arguments. (#814)# Before await session.send({"prompt": "Hello!"}) await session.send_and_wait({"prompt": "What is 2+2?"}) # After await session.send("Hello!") await session.send_and_wait("What is 2+2?")
-
MessageOptions,SessionConfig, andResumeSessionConfigremoved from public API — These TypedDicts are no longer exported. Use the new keyword-argument signatures directly. (#587, #814) -
Internal modules renamed to private —
copilot.jsonrpc,copilot.sdk_protocol_version, andcopilot.telemetryare nowcopilot._jsonrpc,copilot._sdk_protocol_version, andcopilot._telemetry. If you were importing from these modules directly, update your imports. (#884) -
Typed overloads for
CopilotClient.on()— Event registration now uses typed overloads for better autocomplete. This shouldn't break existing code but changes the type signature. (#589)
-
Client.Start()context no longer kills the CLI process — Previously, canceling thecontext.Contextpassed toStart()would terminate the spawned CLI process (it usedexec.CommandContext). Now the CLI process lifespan is independent of that context — callclient.Stop()orclient.ForceStop()to shut it down. (#689) -
LogOptions.Ephemeralchanged fromboolto*bool— This enables proper three-state semantics (unset/true/false). Usecopilot.Bool(true)instead of a baretrue. (#827)// Before session.Log(ctx, copilot.LogOptions{Level: copilot.LevelInfo, Ephemeral: true}, "message") // After session.Log(ctx, copilot.LogOptions{Level: copilot.LevelInfo, Ephemeral: copilot.Bool(true)}, "message")
v0.1.32 (2026-03-07)
SDK applications written against the v3 API now also work when connected to a v2 CLI server, with no code changes required. The SDK detects the server's protocol version and automatically adapts v2 tool.call and permission.request messages into the same user-facing handlers used by v3. (#706)
const session = await client.createSession({
tools: [myTool], // unchanged — works with v2 and v3 servers
onPermissionRequest: approveAll,
});var session = await client.CreateSessionAsync(new SessionConfig {
Tools = [myTool], // unchanged — works with v2 and v3 servers
OnPermissionRequest = approveAll,
});v0.1.31 (2026-03-07)
The SDK now uses protocol version 3, where the runtime broadcasts external_tool.requested and permission.requested as session events to all connected clients. This enables multi-client architectures where different clients contribute different tools, or where multiple clients observe the same permission prompts — if one client approves, all clients see the result. Your existing tool and permission handler code is unchanged. (#686)
// Two clients each register different tools; the agent can use both
const session1 = await client1.createSession({
tools: [defineTool("search", { handler: doSearch })],
onPermissionRequest: approveAll,
});
const session2 = await client2.resumeSession(session1.id, {
tools: [defineTool("analyze", { handler: doAnalyze })],
onPermissionRequest: approveAll,
});var session1 = await client1.CreateSessionAsync(new SessionConfig {
Tools = [AIFunctionFactory.Create(DoSearch, "search")],
OnPermissionRequest = PermissionHandlers.ApproveAll,
});
var session2 = await client2.ResumeSessionAsync(session1.Id, new ResumeSessionConfig {
Tools = [AIFunctionFactory.Create(DoAnalyze, "analyze")],
OnPermissionRequest = PermissionHandlers.ApproveAll,
});Rather than comparing result.Kind against undiscoverable magic strings like "approved" or "denied-interactively-by-user", .NET and Go now provide typed constants. Node and Python already had typed unions for this; this brings full parity. (#631)
session.OnPermissionCompleted += (e) => {
if (e.Result.Kind == PermissionRequestResultKind.Approved) { /* ... */ }
if (e.Result.Kind == PermissionRequestResultKind.DeniedInteractivelyByUser) { /* ... */ }
};// Go: PermissionKindApproved, PermissionKindDeniedByRules,
// PermissionKindDeniedCouldNotRequestFromUser, PermissionKindDeniedInteractivelyByUser
if result.Kind == copilot.PermissionKindApproved { /* ... */ }- feature: [Python] [Go] add
get_last_session_id()/GetLastSessionID()for SDK-wide parity (was already available in Node and .NET) (#671) - improvement: [Python] add
timeoutparameter to generated RPC methods, allowing callers to override the default 30s timeout for long-running operations (#681) - bugfix: [Go]
PermissionRequestfields are now properly typed (ToolName,Diff,Path, etc.) instead of a genericExtra map[string]anycatch-all (#685)
v0.1.30 (2026-03-03)
Applications can now override built-in tools such as grep, edit_file, or read_file. To do this, register a custom tool with the same name and set the override flag. Without the flag, the runtime will return an error if the name clashes with a built-in. (#636)
import { defineTool } from "@github/copilot-sdk";
const session = await client.createSession({
tools: [defineTool("grep", {
overridesBuiltInTool: true,
handler: async (params) => `CUSTOM_GREP_RESULT: ${params.query}`,
})],
onPermissionRequest: approveAll,
});var grep = AIFunctionFactory.Create(
([Description("Search query")] string query) => $"CUSTOM_GREP_RESULT: {query}",
"grep",
"Custom grep implementation",
new AIFunctionFactoryOptions
{
AdditionalProperties = new ReadOnlyDictionary<string, object?>(
new Dictionary<string, object?> { ["is_override"] = true })
});While session.rpc.model.switchTo() already worked, there is now a convenience method directly on the session object. (#621)
- TypeScript:
await session.setModel("gpt-4.1") - C#:
await session.SetModelAsync("gpt-4.1") - Python:
await session.set_model("gpt-4.1") - Go:
err := session.SetModel(ctx, "gpt-4.1")
- improvement: [C#] use event delegate for thread-safe, insertion-ordered event handler dispatch (#624)
- improvement: [C#] deduplicate
OnDisposeCalland improve implementation (#626) - improvement: [C#] remove unnecessary
SemaphoreSlimlocks for handler fields (#625) - bugfix: [Python] correct
PermissionHandler.approve_alltype annotations (#618)
- @giulio-leone made their first contribution in #618