Python SDK for programmatic control of GitHub Copilot CLI via JSON-RPC.
pip install github-copilot-sdkTo include OpenTelemetry support:
pip install "github-copilot-sdk[telemetry]"Try the interactive chat sample (from the repo root):
cd python/samples
python chat.pyimportasynciofromcopilotimportCopilotClientfromcopilot.generated.session_eventsimportAssistantMessageData, SessionIdleDatafromcopilot.sessionimportPermissionHandlerasyncdefmain():
# Client automatically starts on enter and cleans up on exitasyncwithCopilotClient() asclient:
# Create a session with automatic cleanupasyncwithawaitclient.create_session(
on_permission_request=PermissionHandler.approve_all,
model="gpt-5",
) assession:
# Wait for response using session.idle eventdone=asyncio.Event()
defon_event(event):
matchevent.data:
caseAssistantMessageData() asdata:
print(data.content)
caseSessionIdleData():
done.set()
session.on(on_event)
# Send a message and wait for completionawaitsession.send("What is 2+2?")
awaitdone.wait()
asyncio.run(main())If you need more control over the lifecycle, you can call start(), stop(), and disconnect() manually:
importasynciofromcopilotimportCopilotClientfromcopilot.generated.session_eventsimportAssistantMessageData, SessionIdleDatafromcopilot.sessionimportPermissionHandlerasyncdefmain():
client=CopilotClient()
awaitclient.start()
# Create a session (on_permission_request is optional; approve_all allows every tool)session=awaitclient.create_session(
on_permission_request=PermissionHandler.approve_all,
model="gpt-5",
)
done=asyncio.Event()
defon_event(event):
matchevent.data:
caseAssistantMessageData() asdata:
print(data.content)
caseSessionIdleData():
done.set()
session.on(on_event)
awaitsession.send("What is 2+2?")
awaitdone.wait()
# Clean up manuallyawaitsession.disconnect()
awaitclient.stop()
asyncio.run(main())- ✅ Full JSON-RPC protocol support
- ✅ stdio and TCP transports
- ✅ Real-time streaming events
- ✅ Session history with
get_events() - ✅ Type hints throughout
- ✅ Async/await native
- ✅ Async context manager support for automatic resource cleanup
fromcopilotimportCopilotClientfromcopilot.sessionimportPermissionHandlerasyncwithCopilotClient() asclient:
asyncwithawaitclient.create_session(
on_permission_request=PermissionHandler.approve_all,
model="gpt-5",
) assession:
defon_event(event):
print(f"Event: {event.type}")
session.on(on_event)
awaitsession.send("Hello!")
# ... wait for events ...Note: For manual lifecycle management, see Manual Resource Management above.
fromcopilotimportCopilotClient, RuntimeConnection# Connect to an existing CLI serverclient=CopilotClient(connection=RuntimeConnection.for_uri("localhost:3000"))CopilotClient Constructor:
CopilotClient() # spawn the bundled runtime with defaultsCopilotClient(connection=..., log_level="debug", github_token=..., ...)All options are kw-only parameters:
connection(RuntimeConnection | None): How to reach the runtime. UseRuntimeConnection.for_stdio(...),RuntimeConnection.for_tcp(...), orRuntimeConnection.for_uri(...). Defaults to a stdio connection with the bundled binary.working_directory(str | None): Working directory for the CLI process (default: current dir).log_level(str): Log level (default: "info").env(dict | None): Environment variables for the CLI process.github_token(str | None): GitHub token for authentication. When provided, takes priority over other auth methods.base_directory(str | None): Base directory for Copilot data (session state, config, etc.). SetsCOPILOT_HOMEon the spawned CLI process. WhenNone, the CLI defaults to~/.copilot. Useful in restricted environments where only specific directories are writable. Ignored when using aUriRuntimeConnection.use_logged_in_user(bool | None): Whether to use logged-in user for authentication (default: True, but False whengithub_tokenis provided).telemetry(dict | None): OpenTelemetry configuration for the CLI process. Providing this enables telemetry — no separate flag needed. See Telemetry below.session_fs(dict | None): Connection-level session filesystem provider configuration.session_idle_timeout_seconds(int | None): Server-wide session idle timeout in seconds. Set toNoneor0to disable.enable_remote_sessions(bool): Enable remote/cloud session support (default: False).on_list_models(callable | None): Custom handler forlist_models(). When provided, the handler is called instead of querying the runtime.mode(str): Client mode (default:"copilot-cli").
RuntimeConnection variants:
RuntimeConnection.for_stdio(path=None, args=None)— spawn a local CLI process and talk over stdio.RuntimeConnection.for_tcp(port=0, connection_token=None, path=None, args=None)— spawn a local CLI in TCP mode.RuntimeConnection.for_uri(url, connection_token=None)— connect to an existing CLI server (e.g."localhost:8080").
CopilotClient.create_session():
These are passed as keyword arguments to create_session():
model(str): Model to use ("gpt-5", "claude-sonnet-4.5", etc.). Required when using custom provider.reasoning_effort(str): Reasoning effort level for models that support it ("low", "medium", "high", "xhigh"). Uselist_models()to check which models support this option.session_id(str): Custom session IDtools(list): Custom tools exposed to the CLI. Tools withhandler=Noneare declaration-only and must be resolved via pending tool-call RPCs.system_message(SystemMessageConfig): System message configurationstreaming(bool): Enable streaming delta eventsprovider(ProviderConfig): Custom API provider configuration (BYOK). See Custom Providers section.infinite_sessions(InfiniteSessionConfig): Automatic context compaction configurationon_permission_request(callable): Optional handler called before each tool execution to approve or deny it. When omitted, permission requests are emitted as events and left pending for manual resolution. UsePermissionHandler.approve_allto allow everything, or provide a custom function for fine-grained control. See Permission Handling section.on_user_input_request(callable): Handler for user input requests from the agent (enables ask_user tool). See User Input Requests section.hooks(SessionHooks): Hook handlers for session lifecycle events. See Session Hooks section.
Session Lifecycle Methods:
# Get the session currently displayed in TUI (TUI+server mode only)session_id=awaitclient.get_foreground_session_id()
# Request TUI to display a specific session (TUI+server mode only)awaitclient.set_foreground_session_id("session-123")
# Subscribe to all lifecycle eventsdefon_lifecycle(event):
print(f"{event.type}: {event.session_id}")
unsubscribe=client.on_lifecycle(on_lifecycle)
# Subscribe to specific event typeunsubscribe=client.on_lifecycle("session.foreground", lambdae: print(f"Foreground: {e.session_id}"))
# Later, to stop receiving events:unsubscribe()Lifecycle Event Types:
session.created- A new session was createdsession.deleted- A session was deletedsession.updated- A session was updatedsession.foreground- A session became the foreground session in TUIsession.background- A session is no longer the foreground session
Define tools with automatic JSON schema generation using the @define_tool decorator and Pydantic models:
frompydanticimportBaseModel, FieldfromcopilotimportCopilotClient, define_toolclassLookupIssueParams(BaseModel):
id: str=Field(description="Issue identifier")
@define_tool(description="Fetch issue details from our tracker")asyncdeflookup_issue(params: LookupIssueParams) ->str:
issue=awaitfetch_issue(params.id)
returnissue.summaryasyncwithawaitclient.create_session(
on_permission_request=PermissionHandler.approve_all,
model="gpt-5",
tools=[lookup_issue],
) assession:
...Note: When using
from __future__ import annotations, define Pydantic models at module level (not inside functions).
Low-level API (without Pydantic):
For users who prefer manual schema definition:
fromcopilotimportCopilotClientfromcopilot.toolsimportTool, ToolInvocation, ToolResultfromcopilot.sessionimportPermissionHandlerasyncdeflookup_issue(invocation: ToolInvocation) ->ToolResult:
issue_id=invocation.arguments["id"]
issue=awaitfetch_issue(issue_id)
returnToolResult(
text_result_for_llm=issue.summary,
result_type="success",
session_log=f"Fetched issue {issue_id}",
)
asyncwithawaitclient.create_session(
on_permission_request=PermissionHandler.approve_all,
model="gpt-5",
tools=[
Tool(
name="lookup_issue",
description="Fetch issue details from our tracker",
parameters={
"type": "object",
"properties": {
"id": {"type": "string", "description": "Issue identifier"},
},
"required": ["id"],
},
handler=lookup_issue,
)
],
) assession:
...The SDK automatically handles tool.call, executes your handler (sync or async), and responds with the final result when the tool completes. If a tool has no handler, it is exposed as a declaration only; observe external_tool.requested events and resolve the call with the pending tool RPC.
You can also create a declaration-only tool with generated Pydantic parameters:
tool=define_tool(
"lookup_issue",
description="Fetch issue details from our tracker",
params_type=LookupIssueParams,
)If you register a tool with the same name as a built-in CLI tool (e.g. edit_file, read_file), the SDK will throw an error unless you explicitly opt in by setting overrides_built_in_tool=True. This flag signals that you intend to replace the built-in tool with your custom implementation.
classEditFileParams(BaseModel):
path: str=Field(description="File path")
content: str=Field(description="New file content")
@define_tool(name="edit_file", description="Custom file editor with project-specific validation", overrides_built_in_tool=True)asyncdefedit_file(params: EditFileParams) ->str:
# your logicSet skip_permission=True on a tool definition to allow it to execute without triggering a permission prompt:
@define_tool(name="safe_lookup", description="A read-only lookup that needs no confirmation", skip_permission=True)asyncdefsafe_lookup(params: LookupParams) ->str:
# your logicThe SDK supports image attachments via the attachments parameter. You can attach images by providing their file path, or by passing base64-encoded data directly using a blob attachment:
# File attachment — runtime reads from diskawaitsession.send(
"What's in this image?",
attachments=[
{
"type": "file",
"path": "/path/to/image.jpg",
}
],
)
# Blob attachment — provide base64 data directlyawaitsession.send(
"What's in this image?",
attachments=[
{
"type": "blob",
"data": base64_image_data,
"mimeType": "image/png",
}
],
)Supported image formats include JPG, PNG, GIF, and other common image types. The agent's view tool can also read images directly from the filesystem, so you can also ask questions like:
awaitsession.send("What does the most recent jpg in this directory portray?")Enable streaming to receive assistant response chunks as they're generated:
importasynciofromcopilotimportCopilotClientfromcopilot.generated.session_eventsimport (
AssistantMessageData,
AssistantMessageDeltaData,
AssistantReasoningData,
AssistantReasoningDeltaData,
SessionIdleData,
)
fromcopilot.sessionimportPermissionHandlerasyncdefmain():
asyncwithCopilotClient() asclient:
asyncwithawaitclient.create_session(
on_permission_request=PermissionHandler.approve_all,
model="gpt-5",
streaming=True,
) assession:
# Use asyncio.Event to wait for completiondone=asyncio.Event()
defon_event(event):
matchevent.data:
caseAssistantMessageDeltaData() asdata:
# Streaming message chunk - print incrementallydelta=data.delta_contentor""print(delta, end="", flush=True)
caseAssistantReasoningDeltaData() asdata:
# Streaming reasoning chunk (if model supports reasoning)delta=data.delta_contentor""print(delta, end="", flush=True)
caseAssistantMessageData() asdata:
# Final message - complete contentprint("\n--- Final message ---")
print(data.content)
caseAssistantReasoningData() asdata:
# Final reasoning content (if model supports reasoning)print("--- Reasoning ---")
print(data.content)
caseSessionIdleData():
# Session finished processingdone.set()
session.on(on_event)
awaitsession.send("Tell me a short story")
awaitdone.wait() # Wait for streaming to completeasyncio.run(main())When streaming=True:
assistant.message_deltaevents are sent withdelta_contentcontaining incremental textassistant.reasoning_deltaevents are sent withdelta_contentfor reasoning/chain-of-thought (model-dependent)- Accumulate
delta_contentvalues to build the full response progressively - The final
assistant.messageandassistant.reasoningevents contain the complete content
Note: assistant.message and assistant.reasoning (final events) are always sent regardless of streaming setting.
By default, sessions use infinite sessions which automatically manage context window limits through background compaction and persist state to a workspace directory.
# Default: infinite sessions enabled with default thresholdsasyncwithawaitclient.create_session(
on_permission_request=PermissionHandler.approve_all,
model="gpt-5",
) assession:
# Access the workspace path for checkpoints and filesprint(session.workspace_path)
# => ~/.copilot/session-state/{session_id}/# Custom thresholdsasyncwithawaitclient.create_session(
on_permission_request=PermissionHandler.approve_all,
model="gpt-5",
infinite_sessions={
"enabled": True,
"background_compaction_threshold": 0.80, # Start compacting at 80% context usage"buffer_exhaustion_threshold": 0.95, # Block at 95% until compaction completes
},
) assession:
...
# Disable infinite sessionsasyncwithawaitclient.create_session(
on_permission_request=PermissionHandler.approve_all,
model="gpt-5",
infinite_sessions={"enabled": False},
) assession:
...When enabled, sessions emit compaction events:
session.compaction_start- Background compaction startedsession.compaction_complete- Compaction finished (includes token counts)
The SDK supports custom OpenAI-compatible API providers (BYOK - Bring Your Own Key), including local providers like Ollama. When using a custom provider, you must specify the model explicitly.
ProviderConfig fields:
type(str): Provider type -"openai","azure", or"anthropic"(default:"openai")base_url(str): API endpoint URL (required)api_key(str): API key (optional for local providers like Ollama)bearer_token(str): Bearer token for authentication (takes precedence overapi_key)wire_api(str): API format for OpenAI/Azure -"completions"or"responses"(default:"completions")azure(dict): Azure-specific options withapi_version(default:"2024-10-21")
Example with Ollama:
asyncwithawaitclient.create_session(
on_permission_request=PermissionHandler.approve_all,
model="deepseek-coder-v2:16b", # Required when using custom providerprovider={
"type": "openai",
"base_url": "http://localhost:11434/v1", # Ollama endpoint# api_key not required for Ollama
},
) assession:
awaitsession.send("Hello!")Example with custom OpenAI-compatible API:
importosasyncwithawaitclient.create_session(
on_permission_request=PermissionHandler.approve_all,
model="gpt-4",
provider={
"type": "openai",
"base_url": "https://my-api.example.com/v1",
"api_key": os.environ["MY_API_KEY"],
},
) assession:
...Example with Azure OpenAI:
importosasyncwithawaitclient.create_session(
on_permission_request=PermissionHandler.approve_all,
model="gpt-4",
provider={
"type": "azure", # Must be "azure" for Azure endpoints, NOT "openai""base_url": "https://my-resource.openai.azure.com", # Just the host, no path"api_key": os.environ["AZURE_OPENAI_KEY"],
"azure": {
"api_version": "2024-10-21",
},
},
) assession:
...Important notes:
- When using a custom provider, the
modelparameter is required. The SDK will throw an error if no model is specified.- For Azure OpenAI endpoints (
*.openai.azure.com), you must usetype: "azure", nottype: "openai".- The
base_urlshould be just the host (e.g.,https://my-resource.openai.azure.com). Do not include/openai/v1in the URL - the SDK handles path construction automatically.
The SDK supports OpenTelemetry for distributed tracing. Provide a telemetry config to enable trace export and automatic W3C Trace Context propagation.
fromcopilotimportCopilotClientclient=CopilotClient(
telemetry={
"otlp_endpoint": "http://localhost:4318",
},
)TelemetryConfig options:
otlp_endpoint(str): OTLP HTTP endpoint URLfile_path(str): File path for JSON-lines trace outputexporter_type(str):"otlp-http"or"file"source_name(str): Instrumentation scope namecapture_content(bool): Whether to capture message content
Trace context (traceparent/tracestate) is automatically propagated between the SDK and CLI on create_session, resume_session, and send calls, and inbound when the CLI invokes tool handlers.
Install with telemetry extras: pip install "github-copilot-sdk[telemetry]" (provides opentelemetry-api)
An on_permission_request handler is optional when you create or resume a session. When provided, it is called before the agent executes each tool (file writes, shell commands, custom tools, etc.) and returns a decision. When omitted, permission requests are emitted as events and left pending for the consumer to resolve with the pending permission RPC.
Use the built-in PermissionHandler.approve_all helper to allow every tool call without any checks:
fromcopilotimportCopilotClientfromcopilot.sessionimportPermissionHandlersession=awaitclient.create_session(
on_permission_request=PermissionHandler.approve_all,
model="gpt-5",
)Provide your own function to inspect each request and apply custom logic (sync or async):
fromcopilotimportPermissionRequest, PermissionRequestResultfromcopilot.generated.rpcimport (
PermissionDecisionApproveOnce,
PermissionDecisionReject,
)
fromcopilot.generated.session_eventsimportPermissionRequestShelldefon_permission_request(
request: PermissionRequest, invocation: dict
) ->PermissionRequestResult:
# ``PermissionRequest`` is a discriminated union — pattern-match on# the variant class to access the per-kind fields.matchrequest:
casePermissionRequestShell(full_command_text=cmd):
# Deny shell commandsreturnPermissionDecisionReject(feedback=f"Shell denied: {cmd}")
case _:
returnPermissionDecisionApproveOnce()
session=awaitclient.create_session(
on_permission_request=on_permission_request,
model="gpt-5",
)Async handlers are also supported:
asyncdefon_permission_request(
request: PermissionRequest, invocation: dict
) ->PermissionRequestResult:
# Simulate an async approval check (e.g., prompting a user over a network)awaitasyncio.sleep(0)
returnPermissionDecisionApproveOnce()The handler returns a PermissionRequestResult, which is an alias for
PermissionDecision | PermissionNoResult (the generated wire-level
union of every decision variant, plus a small sentinel for v1 servers).
Approval decisions are present-tense — they describe the decision to
apply, not the past-tense outcome reported back on permission.completed
session events.
| Variant | Meaning |
|---|---|
PermissionDecisionApproveOnce() | Allow this single request |
PermissionDecisionReject(feedback="…") | Deny the request (optional feedback string forwarded to the LLM) |
PermissionDecisionUserNotAvailable() | Deny the request because no user is available to confirm it (the default) |
PermissionNoResult() | Leave the request unanswered (only valid with protocol v1; rejected by protocol v2 servers) |
Several richer variants (PermissionDecisionApproveForSession,
PermissionDecisionApproveForLocation, PermissionDecisionApprovePermanently,
…) are available for granting longer-lived approvals; see the generated
copilot.generated.rpc module for the full list.
You may pass on_permission_request when resuming a session too:
session=awaitclient.resume_session(
"session-id",
on_permission_request=PermissionHandler.approve_all,
)To let a specific custom tool bypass the permission prompt entirely, set skip_permission=True on the tool definition. See Skipping Permission Prompts under Tools.
Enable the agent to ask questions to the user using the ask_user tool by providing an on_user_input_request handler:
asyncdefhandle_user_input(request, invocation):
# request["question"] - The question to ask# request.get("choices") - Optional list of choices for multiple choice# request.get("allowFreeform", True) - Whether freeform input is allowedprint(f"Agent asks: {request['question']}")
ifrequest.get("choices"):
print(f"Choices: {', '.join(request['choices'])}")
# Return the user's responsereturn {
"answer": "User's answer here",
"wasFreeform": True, # Whether the answer was freeform (not from choices)
}
asyncwithawaitclient.create_session(
on_permission_request=PermissionHandler.approve_all,
model="gpt-5",
on_user_input_request=handle_user_input,
) assession:
...Hook into session lifecycle events by providing handlers in the hooks configuration:
asyncdefon_pre_tool_use(input, invocation):
print(f"About to run tool: {input['toolName']}")
# Return permission decision and optionally modify argsreturn {
"permissionDecision": "allow", # "allow", "deny", or "ask""modifiedArgs": input.get("toolArgs"), # Optionally modify tool arguments"additionalContext": "Extra context for the model",
}
asyncdefon_post_tool_use(input, invocation):
print(f"Tool {input['toolName']} completed")
return {
"additionalContext": "Post-execution notes",
}
asyncdefon_post_tool_use_failure(input, invocation):
# Fires when a tool's result was a failure. `on_post_tool_use` only fires# on success, so register this handler to observe failed tool calls. The# CLI extracts the failure message and passes it as the `error` field.print(f"Tool {input['toolName']} failed: {input['error']}")
return {
"additionalContext": f"Retry guidance for {input['toolName']}",
}
asyncdefon_user_prompt_submitted(input, invocation):
print(f"User prompt: {input['prompt']}")
return {
"modifiedPrompt": input["prompt"], # Optionally modify the prompt
}
asyncdefon_session_start(input, invocation):
print(f"Session started from: {input['source']}") # "startup", "resume", "new"return {
"additionalContext": "Session initialization context",
}
asyncdefon_session_end(input, invocation):
print(f"Session ended: {input['reason']}")
asyncdefon_error_occurred(input, invocation):
print(f"Error in {input['errorContext']}: {input['error']}")
return {
"errorHandling": "retry", # "retry", "skip", or "abort"
}
asyncwithawaitclient.create_session(
on_permission_request=PermissionHandler.approve_all,
model="gpt-5",
hooks={
"on_pre_tool_use": on_pre_tool_use,
"on_post_tool_use": on_post_tool_use,
"on_post_tool_use_failure": on_post_tool_use_failure,
"on_user_prompt_submitted": on_user_prompt_submitted,
"on_session_start": on_session_start,
"on_session_end": on_session_end,
"on_error_occurred": on_error_occurred,
},
) assession:
...Available hooks:
on_pre_tool_use- Intercept tool calls before execution. Can allow/deny or modify arguments.on_post_tool_use- Process tool results after successful execution. Can modify results or add context.on_post_tool_use_failure- Observe failed tool executions and inject extra context to guide the model's next step.on_user_prompt_submitted- Intercept user prompts. Can modify the prompt before processing.on_session_start- Run logic when a session starts or resumes.on_session_end- Cleanup or logging when session ends.on_error_occurred- Handle errors with retry/skip/abort strategies.
Register slash commands that users can invoke from the CLI TUI. When the user types /commandName, the SDK dispatches the event to your handler.
fromcopilot.sessionimportCommandDefinition, CommandContext, PermissionHandlerasyncdefhandle_deploy(ctx: CommandContext) ->None:
print(f"Deploying with args: {ctx.args}")
# ctx.session_id — the session where the command was invoked# ctx.command — full command text (e.g. "/deploy production")# ctx.command_name — command name without leading / (e.g. "deploy")# ctx.args — raw argument string (e.g. "production")asyncwithawaitclient.create_session(
on_permission_request=PermissionHandler.approve_all,
commands=[
CommandDefinition(
name="deploy",
description="Deploy the app",
handler=handle_deploy,
),
CommandDefinition(
name="rollback",
description="Rollback to previous version",
handler=lambdactx: print("Rolling back..."),
),
],
) assession:
...Commands can also be provided when resuming a session via resume_session(commands=[...]).
The session.ui API provides convenience methods for asking the user questions through interactive dialogs. These methods are only available when the CLI host supports elicitation — check session.capabilities before calling.
ui_caps=session.capabilities.get("ui", {})
ifui_caps.get("elicitation"):
# Safe to call session.ui methods
...Shows a yes/no confirmation dialog:
ok=awaitsession.ui.confirm("Deploy to production?")
ifok:
print("Deploying...")Shows a selection dialog with a list of options:
env=awaitsession.ui.select("Choose environment:", ["staging", "production", "dev"])
ifenv:
print(f"Selected: {env}")Shows a text input dialog with optional constraints:
name=awaitsession.ui.input("Enter your name:")
# With optionsemail=awaitsession.ui.input("Enter email:", {
"title": "Email Address",
"description": "We'll use this for notifications",
"format": "email",
})For full control, use the elicitation() method with a custom JSON schema:
result=awaitsession.ui.elicitation({
"message": "Configure deployment",
"requestedSchema": {
"type": "object",
"properties": {
"region": {"type": "string", "enum": ["us-east-1", "eu-west-1"]},
"replicas": {"type": "number", "minimum": 1, "maximum": 10},
},
"required": ["region"],
},
})
ifresult["action"] =="accept":
region=result["content"]["region"]
replicas=result["content"].get("replicas", 1)When the server (or an MCP tool) needs to ask the end-user a question, it sends an elicitation.requested event. Provide an on_elicitation_request handler to respond:
fromcopilot.sessionimportElicitationContext, ElicitationResult, PermissionHandlerasyncdefhandle_elicitation(
context: ElicitationContext,
) ->ElicitationResult:
# context["session_id"] — the session ID# context["message"] — what the server is asking# context.get("requestedSchema") — optional JSON schema for form fields# context.get("mode") — "form" or "url"print(f"Server asks: {context['message']}")
# Return the user's responsereturn {
"action": "accept", # or "decline" or "cancel""content": {"answer": "yes"},
}
asyncwithawaitclient.create_session(
on_permission_request=PermissionHandler.approve_all,
on_elicitation_request=handle_elicitation,
) assession:
...When on_elicitation_request is provided, the SDK automatically:
- Sends
requestElicitation: trueto the server during session creation/resumption - Reports the
elicitationcapability on the session - Dispatches
elicitation.requestedevents to your handler - Auto-cancels if your handler throws an error (so the server doesn't hang)
- Python 3.11+
- GitHub Copilot CLI installed and accessible