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.
enableConfigDiscovery for automatic MCP and skill config loadingSet enableConfigDiscovery: true when creating a session to let the runtime automatically discover MCP server configurations (.mcp.json, .vscode/mcp.json) and skill directories from the working directory. Discovered settings are merged with any explicitly provided values; explicit values take precedence on name collision. (#1044)
const session = await client.createSession({
enableConfigDiscovery: true,
});
var session = await client.CreateSessionAsync(new SessionConfig {
EnableConfigDiscovery = true,
});
await client.create_session(enable_config_discovery=True)client.CreateSession(ctx, &copilot.SessionConfig{EnableConfigDiscovery: ptr(true)})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): The
onElicitationRequesthandler signature changed from two arguments (request, invocation) to a singleElicitationContextthat combines both. Update callers to usecontext.sessionIdandcontext.messagedirectly.
session.getMetadata across all SDKsEfficiently 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)
const meta = await client.getSessionMetadata(sessionId);var meta = await client.GetSessionMetadataAsync(sessionId);meta = await client.get_session_metadata(session_id)meta, err := client.GetSessionMetadata(ctx, sessionID)sessionFs for virtualizing per-session storage (Node SDK)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)
toolTelemetry, resultType, etc.) now sent via RPC as objects instead of being stringified, preserving metadata for Node, Go, and Python SDKs (#970)CopilotClient and CopilotSession now support async with for automatic resource cleanup (#475)copilot.types module removed; import types directly from copilot (#871)workspace_path now accepts any os.PathLike and session.workspace_path returns a pathlib.Path (#901)rpc package API: renamed structs drop the redundant Rpc infix (e.g. ModelRpcApi → ModelApi) (#905)Session.SetModel now takes a pointer for optional options instead of a variadic argument (#904)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",
},
});
CopilotClient(SubprocessConfig(telemetry={"otlp_endpoint": "http://localhost:4318", "source_name": "my-app"}))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,
});
skipPermission on tool definitions — Tools can now be registered with skipPermission: true to bypass the confirmation prompt for low-risk operations like read-only queries. Available in all four SDKs. (#808)reasoningEffort when switching models — All SDKs now accept an optional reasoningEffort parameter in setModel() for models that support it. (#712)onListModels in client options to override client.listModels() with their own model list. (#730)no-result permission outcome — Permission handlers can now return "no-result" so extensions can attach to sessions without actively answering permission requests. (#802)SessionConfig.onEvent catch-all — A new onEvent handler on session config is registered before the RPC is issued, guaranteeing that early events like session.start are never dropped. (#664)format: "cjs". No changes needed in consumer code. (#546)[Experimental] in C#, /** @experimental */ in TypeScript, and comments in Python and Go. (#875)system.notification events and a session log RPC API. (#737)Client.Start so cancellation no longer kills the child process (#689)DebuggerDisplay attribute to SessionEvent for easier debugging (#726)Task.WhenAny + Task.Delay timeout pattern with .WaitAsync(TimeSpan) (#805)cliPath when cliUrl is 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.
SessionEvent.ToJson() failing for events with JsonElement-backed payloads (assistant.message, tool.execution_start, etc.) (#868)TypeInfoResolver for StreamJsonRpc.RequestId to fix NativeAOT compatibility (#783)autoRestart removed — The autoRestart option 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 to autoRestart from 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.
CopilotClient constructor redesigned — The CopilotClientOptions TypedDict 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() and resume_session() now take keyword arguments instead of a SessionConfig / ResumeSessionConfig TypedDict. on_permission_request is 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() and send_and_wait() take a positional prompt string instead of a MessageOptions TypedDict. 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, and ResumeSessionConfig removed 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, and copilot.telemetry are now copilot._jsonrpc, copilot._sdk_protocol_version, and copilot._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 the context.Context passed to Start() would terminate the spawned CLI process (it used exec.CommandContext). Now the CLI process lifespan is independent of that context — call client.Stop() or client.ForceStop() to shut it down. (#689)
LogOptions.Ephemeral changed from bool to *bool — This enables proper three-state semantics (unset/true/false). Use copilot.Bool(true) instead of a bare true. (#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")
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,
});
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,
});
PermissionRequestResultKind for .NET and GoRather 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 { /* ... */ }
get_last_session_id() / GetLastSessionID() for SDK-wide parity (was already available in Node and .NET) (#671)timeout parameter to generated RPC methods, allowing callers to override the default 30s timeout for long-running operations (#681)PermissionRequest fields are now properly typed (ToolName, Diff, Path, etc.) instead of a generic Extra map[string]any catch-all (#685)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)
await session.setModel("gpt-4.1")await session.SetModelAsync("gpt-4.1")await session.set_model("gpt-4.1")err := session.SetModel(ctx, "gpt-4.1")OnDisposeCall and improve implementation (#626)SemaphoreSlim locks for handler fields (#625)PermissionHandler.approve_all type annotations (#618)Can you improve this documentation? These fine people already did:
github-actions[bot] & Steve SandersonEdit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |