diff --git a/docs/auth/byok.md b/docs/auth/byok.md
index cb2f8cb90..4afb149e8 100644
--- a/docs/auth/byok.md
+++ b/docs/auth/byok.md
@@ -372,8 +372,8 @@ const client = new CopilotClient({
from copilot import CopilotClient
from copilot.client import ModelInfo, ModelCapabilities, ModelSupports, ModelLimits
-client = CopilotClient({
- "on_list_models": lambda: [
+client = CopilotClient(
+ on_list_models=lambda: [
ModelInfo(
id="my-custom-model",
name="My Custom Model",
@@ -383,7 +383,7 @@ client = CopilotClient({
),
)
],
-})
+)
```
diff --git a/docs/features/custom-agents.md b/docs/features/custom-agents.md
index 3d93f7589..716e80583 100644
--- a/docs/features/custom-agents.md
+++ b/docs/features/custom-agents.md
@@ -64,14 +64,13 @@ const session = await client.createSession({
Python
```python
-from copilot import CopilotClient
-from copilot.session import PermissionRequestResult
+from copilot import CopilotClient, PermissionDecisionApproveOnce
client = CopilotClient()
await client.start()
session = await client.create_session(
- on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
+ on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
model="gpt-4.1",
custom_agents=[
{
diff --git a/docs/features/hooks.md b/docs/features/hooks.md
index c88c6e605..6ba554a1e 100644
--- a/docs/features/hooks.md
+++ b/docs/features/hooks.md
@@ -60,14 +60,13 @@ const session = await client.createSession({
Python
```python
-from copilot import CopilotClient
-from copilot.session import PermissionRequestResult
+from copilot import CopilotClient, PermissionDecisionApproveOnce
client = CopilotClient()
await client.start()
session = await client.create_session(
- on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
+ on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
hooks={
"on_session_start": on_session_start,
"on_pre_tool_use": on_pre_tool_use,
@@ -262,7 +261,7 @@ const session = await client.createSession({
Python
```python
-from copilot.session import PermissionRequestResult
+from copilot import PermissionDecisionApproveOnce
READ_ONLY_TOOLS = ["read_file", "glob", "grep", "view"]
@@ -276,7 +275,7 @@ async def on_pre_tool_use(input_data, invocation):
return {"permissionDecision": "allow"}
session = await client.create_session(
- on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
+ on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
hooks={"on_pre_tool_use": on_pre_tool_use},
)
```
@@ -578,7 +577,7 @@ const session = await client.createSession({
```python
import json, aiofiles
-from copilot.session import PermissionRequestResult
+from copilot import PermissionDecisionApproveOnce
audit_log = []
@@ -630,7 +629,7 @@ async def on_session_end(input_data, invocation):
return None
session = await client.create_session(
- on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
+ on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
hooks={
"on_session_start": on_session_start,
"on_user_prompt_submitted": on_user_prompt_submitted,
@@ -709,7 +708,7 @@ const session = await client.createSession({
```python
import subprocess
-from copilot.session import PermissionRequestResult
+from copilot import PermissionDecisionApproveOnce
async def on_session_end(input_data, invocation):
sid = invocation["session_id"][:8]
@@ -728,7 +727,7 @@ async def on_error_occurred(input_data, invocation):
return None
session = await client.create_session(
- on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
+ on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
hooks={
"on_session_end": on_session_end,
"on_error_occurred": on_error_occurred,
@@ -932,7 +931,7 @@ const session = await client.createSession({
Python
```python
-from copilot.session import PermissionRequestResult
+from copilot import PermissionDecisionApproveOnce
session_metrics = {}
@@ -963,7 +962,7 @@ async def on_session_end(input_data, invocation):
return None
session = await client.create_session(
- on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
+ on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
hooks={
"on_session_start": on_session_start,
"on_user_prompt_submitted": on_user_prompt_submitted,
diff --git a/docs/features/image-input.md b/docs/features/image-input.md
index 4aa564558..6f743f1fa 100644
--- a/docs/features/image-input.md
+++ b/docs/features/image-input.md
@@ -68,14 +68,13 @@ await session.send({
Python
```python
-from copilot import CopilotClient
-from copilot.session import PermissionRequestResult
+from copilot import CopilotClient, PermissionDecisionApproveOnce
client = CopilotClient()
await client.start()
session = await client.create_session(
- on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
+ on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
model="gpt-4.1",
)
@@ -286,14 +285,13 @@ await session.send({
Python
```python
-from copilot import CopilotClient
-from copilot.session import PermissionRequestResult
+from copilot import CopilotClient, PermissionDecisionApproveOnce
client = CopilotClient()
await client.start()
session = await client.create_session(
- on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
+ on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
model="gpt-4.1",
)
diff --git a/docs/features/remote-sessions.md b/docs/features/remote-sessions.md
index 391bb762d..33556e43a 100644
--- a/docs/features/remote-sessions.md
+++ b/docs/features/remote-sessions.md
@@ -38,9 +38,9 @@ session.on("session.info", (event) => {
```python
-from copilot import CopilotClient, SubprocessConfig
+from copilot import CopilotClient
-client = CopilotClient(SubprocessConfig(remote=True))
+client = CopilotClient(enable_remote_sessions=True)
session = await client.create_session(
working_directory="/path/to/github-repo",
on_permission_request=lambda req: {"allowed": True},
diff --git a/docs/features/skills.md b/docs/features/skills.md
index 516c11762..05c5a97a9 100644
--- a/docs/features/skills.md
+++ b/docs/features/skills.md
@@ -42,15 +42,14 @@ await session.sendAndWait({ prompt: "Review this code for security issues" });
Python
```python
-from copilot import CopilotClient
-from copilot.session import PermissionRequestResult
+from copilot import CopilotClient, PermissionDecisionApproveOnce
async def main():
client = CopilotClient()
await client.start()
session = await client.create_session(
- on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
+ on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
model="gpt-4.1",
skill_directories=[
"./skills/code-review",
diff --git a/docs/features/steering-and-queueing.md b/docs/features/steering-and-queueing.md
index ce4f4fba2..237457585 100644
--- a/docs/features/steering-and-queueing.md
+++ b/docs/features/steering-and-queueing.md
@@ -69,15 +69,14 @@ await session.send({
Python
```python
-from copilot import CopilotClient
-from copilot.session import PermissionRequestResult
+from copilot import CopilotClient, PermissionDecisionApproveOnce
async def main():
client = CopilotClient()
await client.start()
session = await client.create_session(
- on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
+ on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
model="gpt-4.1",
)
@@ -261,15 +260,14 @@ await session.send({
Python
```python
-from copilot import CopilotClient
-from copilot.session import PermissionRequestResult
+from copilot import CopilotClient, PermissionDecisionApproveOnce
async def main():
client = CopilotClient()
await client.start()
session = await client.create_session(
- on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
+ on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
model="gpt-4.1",
)
@@ -502,7 +500,7 @@ await session.send({
```python
session = await client.create_session(
- on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"),
+ on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
model="gpt-4.1",
)
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 0d5e5887e..f451fa69c 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -665,13 +665,12 @@ unsubscribeIdle();
```python
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionDecisionApproveOnce
from copilot.generated.session_events import SessionEvent, SessionEventType
-from copilot.session import PermissionRequestResult
client = CopilotClient()
-session = await client.create_session(on_permission_request=lambda req, inv: PermissionRequestResult(kind="approve-once"))
+session = await client.create_session(on_permission_request=lambda req, inv: PermissionDecisionApproveOnce())
# Subscribe to all events
unsubscribe = session.on(lambda event: print(f"Event: {event.type}"))
@@ -1968,12 +1967,12 @@ const session = await client.createSession({ onPermissionRequest: approveAll });
Python
```python
-from copilot import CopilotClient
+from copilot import CopilotClient, CopilotClientOptions, RuntimeConnection
from copilot.session import PermissionHandler
-client = CopilotClient({
- "cli_url": "localhost:4321"
-})
+client = CopilotClient(CopilotClientOptions(
+ connection=RuntimeConnection.for_uri("localhost:4321"),
+))
await client.start()
# Use the client normally
@@ -2138,9 +2137,9 @@ Optional peer dependency: `@opentelemetry/api`
```python
-from copilot import CopilotClient, SubprocessConfig
+from copilot import CopilotClient, CopilotClientOptions
-client = CopilotClient(SubprocessConfig(
+client = CopilotClient(CopilotClientOptions(
telemetry={
"otlp_endpoint": "http://localhost:4318",
},
diff --git a/docs/observability/opentelemetry.md b/docs/observability/opentelemetry.md
index 42a5c6e96..1f9581ba5 100644
--- a/docs/observability/opentelemetry.md
+++ b/docs/observability/opentelemetry.md
@@ -27,13 +27,13 @@ const client = new CopilotClient({
```python
-from copilot import CopilotClient, SubprocessConfig
+from copilot import CopilotClient
-client = CopilotClient(SubprocessConfig(
+client = CopilotClient(
telemetry={
"otlp_endpoint": "http://localhost:4318",
},
-))
+)
```
diff --git a/docs/setup/backend-services.md b/docs/setup/backend-services.md
index 0552ee36e..dfe9c19af 100644
--- a/docs/setup/backend-services.md
+++ b/docs/setup/backend-services.md
@@ -144,10 +144,12 @@ res.json({ content: response?.data.content });
Python
```python
-from copilot import CopilotClient, ExternalServerConfig
+from copilot import CopilotClient, RuntimeConnection
from copilot.session import PermissionHandler
-client = CopilotClient(ExternalServerConfig(url="localhost:4321"))
+client = CopilotClient(
+ connection=RuntimeConnection.for_uri("localhost:4321"),
+)
await client.start()
session = await client.create_session(on_permission_request=PermissionHandler.approve_all, model="gpt-4.1", session_id=f"user-{user_id}-{int(time.time())}")
diff --git a/docs/troubleshooting/debugging.md b/docs/troubleshooting/debugging.md
index 3beadb99f..95c9b3a7d 100644
--- a/docs/troubleshooting/debugging.md
+++ b/docs/troubleshooting/debugging.md
@@ -34,7 +34,7 @@ const client = new CopilotClient({
```python
from copilot import CopilotClient
-client = CopilotClient({"log_level": "debug"})
+client = CopilotClient(log_level="debug")
```
@@ -131,7 +131,7 @@ const client = new CopilotClient({
```
> [!NOTE]
-> Python SDK logging configuration is limited. For advanced logging, run the CLI manually with `--log-dir` and connect via `cli_url`.
+> Python SDK logging configuration is limited. For advanced logging, run the CLI manually with `--log-dir` and connect via `RuntimeConnection.for_uri(...)`.
diff --git a/python/README.md b/python/README.md
index 3cee037cc..4e415e320 100644
--- a/python/README.md
+++ b/python/README.md
@@ -103,7 +103,7 @@ asyncio.run(main())
- ✅ Full JSON-RPC protocol support
- ✅ stdio and TCP transports
- ✅ Real-time streaming events
-- ✅ Session history with `get_messages()`
+- ✅ Session history with `get_events()`
- ✅ Type hints throughout
- ✅ Async/await native
- ✅ Async context manager support for automatic resource cleanup
@@ -113,7 +113,7 @@ asyncio.run(main())
### CopilotClient
```python
-from copilot import CopilotClient, SubprocessConfig
+from copilot import CopilotClient
from copilot.session import PermissionHandler
async with CopilotClient() as client:
@@ -133,40 +133,39 @@ async with CopilotClient() as client:
> **Note:** For manual lifecycle management, see [Manual Resource Management](#manual-resource-management) above.
```python
-from copilot import CopilotClient, ExternalServerConfig
+from copilot import CopilotClient, RuntimeConnection
# Connect to an existing CLI server
-client = CopilotClient(ExternalServerConfig(url="localhost:3000"))
+client = CopilotClient(connection=RuntimeConnection.for_uri("localhost:3000"))
```
**CopilotClient Constructor:**
```python
-CopilotClient(
- config=None, # SubprocessConfig | ExternalServerConfig | None
- *,
- auto_start=True, # auto-start server on first use
- on_list_models=None, # custom handler for list_models()
-)
+CopilotClient() # spawn the bundled runtime with defaults
+CopilotClient(connection=..., log_level="debug", github_token=..., ...)
```
-**SubprocessConfig** — spawn a local CLI process:
+All options are kw-only parameters:
-- `cli_path` (str | None): Path to CLI executable (default: `COPILOT_CLI_PATH` env var, or bundled binary)
-- `cli_args` (list[str]): Extra arguments for the CLI executable
-- `cwd` (str | None): Working directory for CLI process (default: current dir)
-- `use_stdio` (bool): Use stdio transport instead of TCP (default: True)
-- `port` (int): Server port for TCP mode (default: 0 for random)
-- `log_level` (str): Log level (default: "info")
-- `env` (dict | None): Environment variables for the CLI process
+- `connection` (RuntimeConnection | None): How to reach the runtime. Use
+ `RuntimeConnection.for_stdio(...)`, `RuntimeConnection.for_tcp(...)`, or
+ `RuntimeConnection.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.
-- `copilot_home` (str | None): Base directory for Copilot data (session state, config, etc.). Sets `COPILOT_HOME` on the spawned CLI process. When `None`, the CLI defaults to `~/.copilot`. Useful in restricted environments where only specific directories are writable. Ignored when using `ExternalServerConfig`.
+- `base_directory` (str | None): Base directory for Copilot data (session state, config, etc.). Sets `COPILOT_HOME` on the spawned CLI process. When `None`, the CLI defaults to `~/.copilot`. Useful in restricted environments where only specific directories are writable. Ignored when using a `UriRuntimeConnection`.
- `use_logged_in_user` (bool | None): Whether to use logged-in user for authentication (default: True, but False when `github_token` is provided).
- `telemetry` (dict | None): OpenTelemetry configuration for the CLI process. Providing this enables telemetry — no separate flag needed. See [Telemetry](#telemetry) below.
+- `enable_remote_sessions` (bool): Enable remote/cloud session support (default: False).
+- `on_list_models` (callable | None): Custom handler for `list_models()`. When provided, the handler is called instead of querying the runtime.
-**ExternalServerConfig** — connect to an existing CLI server:
+**RuntimeConnection variants:**
-- `url` (str): Server URL (e.g., `"localhost:8080"`, `"http://127.0.0.1:9000"`, or just `"8080"`).
+- `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()`:**
@@ -195,12 +194,12 @@ await client.set_foreground_session_id("session-123")
# Subscribe to all lifecycle events
def on_lifecycle(event):
- print(f"{event.type}: {event.sessionId}")
+ print(f"{event.type}: {event.session_id}")
-unsubscribe = client.on(on_lifecycle)
+unsubscribe = client.on_lifecycle(on_lifecycle)
# Subscribe to specific event type
-unsubscribe = client.on("session.foreground", lambda e: print(f"Foreground: {e.sessionId}"))
+unsubscribe = client.on_lifecycle("session.foreground", lambda e: print(f"Foreground: {e.session_id}"))
# Later, to stop receiving events:
unsubscribe()
@@ -531,13 +530,13 @@ async with await client.create_session(
The SDK supports OpenTelemetry for distributed tracing. Provide a `telemetry` config to enable trace export and automatic W3C Trace Context propagation.
```python
-from copilot import CopilotClient, SubprocessConfig
+from copilot import CopilotClient
-client = CopilotClient(SubprocessConfig(
+client = CopilotClient(
telemetry={
"otlp_endpoint": "http://localhost:4318",
},
-))
+)
```
**TelemetryConfig options:**
@@ -575,31 +574,27 @@ session = await client.create_session(
Provide your own function to inspect each request and apply custom logic (sync or async):
```python
-from copilot.session import PermissionRequestResult
-from copilot.generated.session_events import PermissionRequest
+from copilot import (
+ PermissionDecisionApproveOnce,
+ PermissionDecisionReject,
+ PermissionRequest,
+ PermissionRequestResult,
+ PermissionRequestShell,
+)
+
def on_permission_request(
request: PermissionRequest, invocation: dict
) -> PermissionRequestResult:
- # request.kind — what type of operation is being requested:
- # "shell" — executing a shell command
- # "write" — writing or editing a file
- # "read" — reading a file
- # "mcp" — calling an MCP tool
- # "custom-tool" — calling one of your registered tools
- # "url" — fetching a URL
- # "memory" — accessing or updating session/workspace memory
- # "hook" — invoking a registered hook
- # request.tool_call_id — the tool call that triggered this request
- # request.tool_name — name of the tool (for custom-tool / mcp)
- # request.file_name — file being written (for write)
- # request.full_command_text — full shell command (for shell)
-
- if request.kind.value == "shell":
- # Deny shell commands
- return PermissionRequestResult(kind="reject")
-
- return PermissionRequestResult(kind="approve-once")
+ # ``PermissionRequest`` is a discriminated union — pattern-match on
+ # the variant class to access the per-kind fields.
+ match request:
+ case PermissionRequestShell(full_command_text=cmd):
+ # Deny shell commands
+ return PermissionDecisionReject(feedback=f"Shell denied: {cmd}")
+ case _:
+ return PermissionDecisionApproveOnce()
+
session = await client.create_session(
on_permission_request=on_permission_request,
@@ -615,19 +610,29 @@ async def on_permission_request(
) -> PermissionRequestResult:
# Simulate an async approval check (e.g., prompting a user over a network)
await asyncio.sleep(0)
- return PermissionRequestResult(kind="approve-once")
+ return PermissionDecisionApproveOnce()
```
### Permission Result Kinds
-The handler must return a `PermissionRequestResult` with one of the kinds declared by the `PermissionRequestResultKind` type. Approval decisions are present-tense — they describe the decision to apply, not the past-tense outcome reported back on `permission.completed` session events.
-
-| `kind` value | Meaning |
-| ---------------------- | ------------------------------------------------------------------------------------------- |
-| `"approve-once"` | Allow this single request |
-| `"reject"` | Deny the request |
-| `"user-not-available"` | Deny the request because no user is available to confirm it (the default) |
-| `"no-result"` | Leave the request unanswered (only valid with protocol v1; rejected by protocol v2 servers) |
+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.
### Resuming Sessions
diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py
index c7a37ea0b..ee53264d2 100644
--- a/python/copilot/__init__.py
+++ b/python/copilot/__init__.py
@@ -5,16 +5,50 @@
"""
from .client import (
+ ChildProcessRuntimeConnection,
CloudSessionOptions,
CloudSessionRepository,
CopilotClient,
- ExternalServerConfig,
+ GetAuthStatusResponse,
+ GetStatusResponse,
+ LogLevel,
+ ModelBilling,
+ ModelCapabilities,
ModelCapabilitiesOverride,
+ ModelInfo,
+ ModelLimits,
ModelLimitsOverride,
+ ModelPolicy,
+ ModelSupports,
ModelSupportsOverride,
+ ModelVisionLimits,
ModelVisionLimitsOverride,
+ PingResponse,
RemoteSessionMode,
- SubprocessConfig,
+ RuntimeConnection,
+ SessionBackgroundEvent,
+ SessionContext,
+ SessionCreatedEvent,
+ SessionDeletedEvent,
+ SessionForegroundEvent,
+ SessionLifecycleEvent,
+ SessionLifecycleEventBase,
+ SessionLifecycleEventMetadata,
+ SessionLifecycleEventType,
+ SessionLifecycleHandler,
+ SessionListFilter,
+ SessionMetadata,
+ SessionUpdatedEvent,
+ StdioRuntimeConnection,
+ StopError,
+ TcpRuntimeConnection,
+ TelemetryConfig,
+ UriRuntimeConnection,
+)
+from .generated.session_events import (
+ PermissionRequest,
+ SessionEvent,
+ SessionEventType,
)
from .session import (
AutoModeSwitchHandler,
@@ -28,16 +62,50 @@
ElicitationHandler,
ElicitationParams,
ElicitationResult,
+ ErrorOccurredHandler,
+ ErrorOccurredHookInput,
+ ErrorOccurredHookOutput,
ExitPlanModeHandler,
ExitPlanModeRequest,
ExitPlanModeResult,
+ InfiniteSessionConfig,
InputOptions,
+ MCPHTTPServerConfig,
+ MCPServerConfig,
+ MCPStdioServerConfig,
+ PermissionHandler,
+ PermissionNoResult,
+ PermissionRequestResult,
+ PostToolUseHandler,
+ PostToolUseHookInput,
+ PostToolUseHookOutput,
+ PreMcpToolCallHandler,
+ PreMcpToolCallHookInput,
+ PreMcpToolCallHookOutput,
+ PreToolUseHandler,
+ PreToolUseHookInput,
+ PreToolUseHookOutput,
ProviderConfig,
SessionCapabilities,
+ SessionEndHandler,
+ SessionEndHookInput,
+ SessionEndHookOutput,
+ SessionEventHandler,
SessionFsCapabilities,
SessionFsConfig,
+ SessionHooks,
+ SessionStartHandler,
+ SessionStartHookInput,
+ SessionStartHookOutput,
SessionUiApi,
SessionUiCapabilities,
+ SystemMessageConfig,
+ UserInputHandler,
+ UserInputRequest,
+ UserInputResponse,
+ UserPromptSubmittedHandler,
+ UserPromptSubmittedHookInput,
+ UserPromptSubmittedHookOutput,
)
from .session_fs_provider import (
SessionFsFileInfo,
@@ -51,6 +119,7 @@
ToolBinaryResult,
ToolInvocation,
ToolResult,
+ ToolResultType,
convert_mcp_call_tool_result,
define_tool,
)
@@ -58,46 +127,113 @@
__version__ = "0.1.0"
__all__ = [
- "CommandContext",
"AutoModeSwitchHandler",
"AutoModeSwitchRequest",
"AutoModeSwitchResponse",
- "CommandDefinition",
+ "ChildProcessRuntimeConnection",
"CloudSessionOptions",
"CloudSessionRepository",
+ "CommandContext",
+ "CommandDefinition",
"CopilotClient",
"CopilotSession",
"CreateSessionFsHandler",
+ "ElicitationContext",
"ElicitationHandler",
"ElicitationParams",
- "ElicitationContext",
"ElicitationResult",
+ "ErrorOccurredHandler",
+ "ErrorOccurredHookInput",
+ "ErrorOccurredHookOutput",
"ExitPlanModeHandler",
"ExitPlanModeRequest",
"ExitPlanModeResult",
- "ExternalServerConfig",
+ "GetAuthStatusResponse",
+ "GetStatusResponse",
+ "InfiniteSessionConfig",
"InputOptions",
+ "LogLevel",
+ "MCPHTTPServerConfig",
+ "MCPServerConfig",
+ "MCPStdioServerConfig",
+ "ModelBilling",
+ "ModelCapabilities",
"ModelCapabilitiesOverride",
+ "ModelInfo",
+ "ModelLimits",
"ModelLimitsOverride",
+ "ModelPolicy",
+ "ModelSupports",
"ModelSupportsOverride",
+ "ModelVisionLimits",
"ModelVisionLimitsOverride",
+ "PermissionHandler",
+ "PermissionNoResult",
+ "PermissionRequest",
+ "PermissionRequestResult",
+ "PingResponse",
+ "PostToolUseHandler",
+ "PostToolUseHookInput",
+ "PostToolUseHookOutput",
+ "PreMcpToolCallHandler",
+ "PreMcpToolCallHookInput",
+ "PreMcpToolCallHookOutput",
+ "PreToolUseHandler",
+ "PreToolUseHookInput",
+ "PreToolUseHookOutput",
"ProviderConfig",
"RemoteSessionMode",
+ "RuntimeConnection",
+ "SessionBackgroundEvent",
"SessionCapabilities",
+ "SessionContext",
+ "SessionCreatedEvent",
+ "SessionDeletedEvent",
+ "SessionEndHandler",
+ "SessionEndHookInput",
+ "SessionEndHookOutput",
+ "SessionEvent",
+ "SessionEventHandler",
+ "SessionEventType",
+ "SessionForegroundEvent",
"SessionFsCapabilities",
"SessionFsConfig",
"SessionFsFileInfo",
"SessionFsProvider",
"SessionFsSqliteProvider",
"SessionFsSqliteQueryResult",
- "create_session_fs_adapter",
+ "SessionHooks",
+ "SessionLifecycleEvent",
+ "SessionLifecycleEventBase",
+ "SessionLifecycleEventMetadata",
+ "SessionLifecycleEventType",
+ "SessionLifecycleHandler",
+ "SessionListFilter",
+ "SessionMetadata",
+ "SessionStartHandler",
+ "SessionStartHookInput",
+ "SessionStartHookOutput",
"SessionUiApi",
"SessionUiCapabilities",
- "SubprocessConfig",
+ "SessionUpdatedEvent",
+ "StdioRuntimeConnection",
+ "StopError",
+ "SystemMessageConfig",
+ "TcpRuntimeConnection",
+ "TelemetryConfig",
"Tool",
"ToolBinaryResult",
"ToolInvocation",
"ToolResult",
+ "ToolResultType",
+ "UriRuntimeConnection",
+ "UserInputHandler",
+ "UserInputRequest",
+ "UserInputResponse",
+ "UserPromptSubmittedHandler",
+ "UserPromptSubmittedHookInput",
+ "UserPromptSubmittedHookOutput",
"convert_mcp_call_tool_result",
+ "create_session_fs_adapter",
"define_tool",
]
diff --git a/python/copilot/client.py b/python/copilot/client.py
index 7af3fb39f..b9f67ab9d 100644
--- a/python/copilot/client.py
+++ b/python/copilot/client.py
@@ -25,12 +25,12 @@
import threading
import time
import uuid
-from collections.abc import Awaitable, Callable
-from dataclasses import KW_ONLY, dataclass, field
+from collections.abc import Awaitable, Callable, Sequence
+from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path
from types import TracebackType
-from typing import Any, Literal, TypedDict, cast, overload
+from typing import Any, ClassVar, Literal, TypedDict, cast, overload
from ._diagnostics import log_timing
from ._jsonrpc import JsonRpcClient, JsonRpcError, ProcessExitedError
@@ -39,6 +39,7 @@
from .generated.rpc import (
ClientSessionApiHandlers,
ConnectRequest,
+ PermissionDecisionUserNotAvailable,
RemoteSessionMode,
ServerRpc,
_InternalServerRpc,
@@ -46,8 +47,8 @@
register_client_session_api_handlers,
)
from .generated.session_events import (
- PermissionRequest,
SessionEvent,
+ _load_PermissionRequest,
session_event_from_dict,
)
from .session import (
@@ -61,6 +62,7 @@
ExitPlanModeHandler,
InfiniteSessionConfig,
MCPServerConfig,
+ PermissionNoResult,
ProviderConfig,
ReasoningEffort,
SectionTransformFn,
@@ -79,7 +81,7 @@
# Connection Types
# ============================================================================
-ConnectionState = Literal["disconnected", "connecting", "connected", "error"]
+_ConnectionState = Literal["disconnected", "connecting", "connected", "error"]
LogLevel = Literal["none", "error", "warning", "info", "debug", "all"]
@@ -154,112 +156,149 @@ class TelemetryConfig(TypedDict, total=False):
@dataclass
-class SubprocessConfig:
- """Config for spawning a local Copilot CLI subprocess.
+class RuntimeConnection:
+ """Discriminated config describing how to reach the Copilot runtime.
+
+ Construct via the static factories :meth:`stdio`, :meth:`tcp`, or
+ :meth:`uri`. Each factory returns the matching subclass; pattern-match
+ on the subclass (or :func:`isinstance`) to branch on the transport.
Example:
- >>> config = SubprocessConfig(github_token="ghp_...")
- >>> client = CopilotClient(config)
-
- >>> # Custom CLI path with TCP transport
- >>> config = SubprocessConfig(
- ... cli_path="/usr/local/bin/copilot",
- ... use_stdio=False,
- ... log_level="debug",
- ... )
+ >>> CopilotClient() # default: stdio with the bundled runtime
+ >>> CopilotClient(connection=RuntimeConnection.for_uri("localhost:3000"))
"""
- cli_path: str | None = None
- """Path to the Copilot CLI executable. ``None`` uses the bundled binary."""
+ @staticmethod
+ def for_stdio(
+ *,
+ path: str | None = None,
+ args: Sequence[str] = (),
+ ) -> StdioRuntimeConnection:
+ """Spawn a runtime child process and communicate over its stdin/stdout.
- cli_args: list[str] = field(default_factory=list)
- """Extra arguments passed to the CLI executable (inserted before SDK-managed args)."""
+ This is the default when no :attr:`CopilotClientOptions.connection`
+ is supplied.
- _: KW_ONLY
+ Args:
+ path: Path to the runtime executable. When ``None``, uses the
+ bundled binary.
+ args: Extra command-line arguments passed to the runtime process.
+ """
+ return StdioRuntimeConnection(path=path, args=tuple(args))
- working_directory: str | None = None
- """Working directory for the CLI process. ``None`` uses the current directory."""
+ @staticmethod
+ def for_tcp(
+ *,
+ port: int = 0,
+ connection_token: str | None = None,
+ path: str | None = None,
+ args: Sequence[str] = (),
+ ) -> TcpRuntimeConnection:
+ """Spawn a runtime child process listening on a TCP socket.
- use_stdio: bool = True
- """Use stdio transport (``True``, default) or TCP (``False``)."""
+ Args:
+ port: TCP port to listen on. ``0`` (the default) auto-allocates
+ a free port. If the chosen port is already in use, startup
+ fails.
+ connection_token: Optional shared secret the SDK sends to the
+ spawned runtime to authenticate the TCP connection. When
+ ``None``, a UUID is generated automatically so the loopback
+ listener is safe by default.
+ path: Path to the runtime executable. When ``None``, uses the
+ bundled binary.
+ args: Extra command-line arguments passed to the runtime process.
+ """
+ return TcpRuntimeConnection(
+ path=path,
+ args=tuple(args),
+ port=port,
+ connection_token=connection_token,
+ )
- tcp_connection_token: str | None = None
- """Connection token for the headless CLI server (TCP only).
+ @staticmethod
+ def for_uri(url: str, *, connection_token: str | None = None) -> UriRuntimeConnection:
+ """Connect to an already-running runtime at the given URL.
- Only meaningful when ``use_stdio=False``. When the SDK spawns the CLI in TCP mode and
- this is omitted, a UUID is generated automatically so the loopback listener is safe by
- default. Combining this with ``use_stdio=True`` raises :class:`ValueError`.
- """
+ Args:
+ url: URL of the runtime to connect to. Accepts ``"port"``,
+ ``"host:port"``, or a full URL.
+ connection_token: Optional shared secret to authenticate the
+ connection. Required when the server was started with a
+ token; ignored by legacy servers without ``connect`` support.
+ """
+ return UriRuntimeConnection(url=url, connection_token=connection_token)
- port: int = 0
- """TCP port for the CLI server (only when ``use_stdio=False``). 0 means random."""
- log_level: LogLevel = "info"
- """Log level for the CLI process."""
+@dataclass
+class ChildProcessRuntimeConnection(RuntimeConnection):
+ """Base for :class:`RuntimeConnection` variants that spawn a runtime child process.
- env: dict[str, str] | None = None
- """Environment variables for the CLI process. ``None`` inherits the current env."""
+ Construct via :meth:`RuntimeConnection.stdio` or :meth:`RuntimeConnection.tcp`.
+ """
- github_token: str | None = None
- """GitHub token for authentication. Takes priority over other auth methods."""
+ path: str | None = None
+ """Path to the runtime executable. ``None`` uses the bundled binary."""
- copilot_home: str | None = None
- """Base directory for Copilot data (session state, config, etc.).
+ args: Sequence[str] = ()
+ """Extra command-line arguments passed to the runtime process."""
- Sets the ``COPILOT_HOME`` environment variable on the spawned CLI process.
- When ``None``, the CLI defaults to ``~/.copilot``.
- This option is only used when the SDK spawns the CLI process.
- """
- use_logged_in_user: bool | None = None
- """Use the logged-in user for authentication.
+@dataclass
+class StdioRuntimeConnection(ChildProcessRuntimeConnection):
+ """Spawns a runtime child process and communicates over its stdin/stdout.
- ``None`` (default) resolves to ``True`` unless ``github_token`` is set.
+ Construct via :meth:`RuntimeConnection.stdio`.
"""
- telemetry: TelemetryConfig | None = None
- """OpenTelemetry configuration. Providing this enables telemetry — no separate flag needed."""
- session_fs: SessionFsConfig | None = None
- """Connection-level session filesystem provider configuration."""
-
- session_idle_timeout_seconds: int | None = None
- """Server-wide session idle timeout in seconds.
+@dataclass
+class TcpRuntimeConnection(ChildProcessRuntimeConnection):
+ """Spawns a runtime child process listening on a TCP socket.
- Sessions without activity for this duration are automatically cleaned up.
- Set to ``None`` or ``0`` to disable (sessions live indefinitely).
- This option is only used when the SDK spawns the CLI process.
+ Construct via :meth:`RuntimeConnection.tcp`.
"""
- remote: bool = False
- """Enable remote session support (Mission Control integration).
+ port: int = 0
+ """TCP port to listen on. ``0`` (the default) auto-allocates a free port."""
- When ``True``, sessions in a GitHub repository working directory are
- accessible from GitHub web and mobile.
- This option is only used when the SDK spawns the CLI process.
- """
+ connection_token: str | None = None
+ """Shared secret the SDK sends to the spawned runtime. ``None`` auto-generates one."""
@dataclass
-class ExternalServerConfig:
- """Config for connecting to an existing Copilot CLI server over TCP.
+class UriRuntimeConnection(RuntimeConnection):
+ """Connects to an already-running runtime at the specified URL.
- Example:
- >>> config = ExternalServerConfig(url="localhost:3000")
- >>> client = CopilotClient(config)
+ Construct via :meth:`RuntimeConnection.uri`.
"""
- url: str
- """Server URL. Supports ``"host:port"``, ``"http://host:port"``, or just ``"port"``."""
+ url: str = ""
+ """URL of the runtime to connect to. Accepts ``"port"``, ``"host:port"``, or a full URL."""
+
+ connection_token: str | None = None
+ """Shared secret to authenticate the connection."""
- _: KW_ONLY
- tcp_connection_token: str | None = None
- """Connection token sent in the ``connect`` handshake. Required when the server was
- started with a token; ignored by legacy servers without ``connect`` support."""
+@dataclass
+class _CopilotClientOptions:
+ """Internal configuration carrier used by :class:`CopilotClient`.
+
+ This is not part of the public API: ``CopilotClient`` accepts all of
+ these options as keyword arguments directly.
+ """
+ connection: RuntimeConnection | None = None
+ working_directory: str | None = None
+ log_level: LogLevel = "info"
+ env: dict[str, str] | None = None
+ github_token: str | None = None
+ base_directory: str | None = None
+ use_logged_in_user: bool | None = None
+ telemetry: TelemetryConfig | None = None
session_fs: SessionFsConfig | None = None
- """Connection-level session filesystem provider configuration."""
+ session_idle_timeout_seconds: int | None = None
+ enable_remote_sessions: bool = False
+ on_list_models: Callable[[], list[ModelInfo] | Awaitable[list[ModelInfo]]] | None = None
# ============================================================================
@@ -272,32 +311,32 @@ class PingResponse:
"""Response from ping"""
message: str # Echo message with "pong: " prefix
- timestamp: datetime # ISO 8601 timestamp when the ping was processed
- protocolVersion: int # Protocol version for SDK compatibility
+ timestamp: datetime # Timestamp when the ping was processed
+ protocol_version: int # Protocol version for SDK compatibility
@staticmethod
def from_dict(obj: Any) -> PingResponse:
assert isinstance(obj, dict)
message = obj.get("message")
timestamp = obj.get("timestamp")
- protocolVersion = obj.get("protocolVersion")
- if message is None or timestamp is None or protocolVersion is None:
+ protocol_version = obj.get("protocolVersion")
+ if message is None or timestamp is None or protocol_version is None:
raise ValueError(
f"Missing required fields in PingResponse: message={message}, "
- f"timestamp={timestamp}, protocolVersion={protocolVersion}"
+ f"timestamp={timestamp}, protocolVersion={protocol_version}"
)
timestamp_value = (
datetime.fromtimestamp(timestamp / 1000, tz=UTC)
if isinstance(timestamp, (int, float))
else from_datetime(timestamp)
)
- return PingResponse(str(message), timestamp_value, int(protocolVersion))
+ return PingResponse(str(message), timestamp_value, int(protocol_version))
def to_dict(self) -> dict:
result: dict = {}
result["message"] = self.message
result["timestamp"] = self.timestamp.isoformat()
- result["protocolVersion"] = self.protocolVersion
+ result["protocolVersion"] = self.protocol_version
return result
@@ -329,24 +368,24 @@ class GetStatusResponse:
"""Response from status.get"""
version: str # Package version (e.g., "1.0.0")
- protocolVersion: int # Protocol version for SDK compatibility
+ protocol_version: int # Protocol version for SDK compatibility
@staticmethod
def from_dict(obj: Any) -> GetStatusResponse:
assert isinstance(obj, dict)
version = obj.get("version")
- protocolVersion = obj.get("protocolVersion")
- if version is None or protocolVersion is None:
+ protocol_version = obj.get("protocolVersion")
+ if version is None or protocol_version is None:
raise ValueError(
f"Missing required fields in GetStatusResponse: version={version}, "
- f"protocolVersion={protocolVersion}"
+ f"protocolVersion={protocol_version}"
)
- return GetStatusResponse(str(version), int(protocolVersion))
+ return GetStatusResponse(str(version), int(protocol_version))
def to_dict(self) -> dict:
result: dict = {}
result["version"] = self.version
- result["protocolVersion"] = self.protocolVersion
+ result["protocolVersion"] = self.protocol_version
return result
@@ -678,7 +717,7 @@ class SessionContext:
"""Working directory context for a session"""
working_directory: str # Working directory where the session was created
- gitRoot: str | None = None # Git repository root (if in a git repo)
+ git_root: str | None = None # Git repository root (if in a git repo)
repository: str | None = None # GitHub repository in "owner/repo" format
branch: str | None = None # Current git branch
@@ -690,15 +729,15 @@ def from_dict(obj: Any) -> SessionContext:
raise ValueError("Missing required field 'cwd' in SessionContext")
return SessionContext(
working_directory=str(cwd),
- gitRoot=obj.get("gitRoot"),
+ git_root=obj.get("gitRoot"),
repository=obj.get("repository"),
branch=obj.get("branch"),
)
def to_dict(self) -> dict:
result: dict = {"cwd": self.working_directory}
- if self.gitRoot is not None:
- result["gitRoot"] = self.gitRoot
+ if self.git_root is not None:
+ result["gitRoot"] = self.git_root
if self.repository is not None:
result["repository"] = self.repository
if self.branch is not None:
@@ -711,7 +750,7 @@ class SessionListFilter:
"""Filter options for listing sessions"""
working_directory: str | None = None # Filter by exact working directory match
- gitRoot: str | None = None # Filter by git root
+ git_root: str | None = None # Filter by git root
repository: str | None = None # Filter by repository (owner/repo format)
branch: str | None = None # Filter by branch
@@ -719,8 +758,8 @@ def to_dict(self) -> dict:
result: dict = {}
if self.working_directory is not None:
result["cwd"] = self.working_directory
- if self.gitRoot is not None:
- result["gitRoot"] = self.gitRoot
+ if self.git_root is not None:
+ result["gitRoot"] = self.git_root
if self.repository is not None:
result["repository"] = self.repository
if self.branch is not None:
@@ -732,43 +771,43 @@ def to_dict(self) -> dict:
class SessionMetadata:
"""Metadata about a session"""
- sessionId: str # Session identifier
- startTime: str # ISO 8601 timestamp when session was created
- modifiedTime: str # ISO 8601 timestamp when session was last modified
- isRemote: bool # Whether the session is remote
+ session_id: str # Session identifier
+ start_time: datetime # Timestamp when session was created
+ modified_time: datetime # Timestamp when session was last modified
+ is_remote: bool # Whether the session is remote
summary: str | None = None # Optional summary of the session
context: SessionContext | None = None # Working directory context
@staticmethod
def from_dict(obj: Any) -> SessionMetadata:
assert isinstance(obj, dict)
- sessionId = obj.get("sessionId")
- startTime = obj.get("startTime")
- modifiedTime = obj.get("modifiedTime")
- isRemote = obj.get("isRemote")
- if sessionId is None or startTime is None or modifiedTime is None or isRemote is None:
+ session_id = obj.get("sessionId")
+ start_time = obj.get("startTime")
+ modified_time = obj.get("modifiedTime")
+ is_remote = obj.get("isRemote")
+ if session_id is None or start_time is None or modified_time is None or is_remote is None:
raise ValueError(
- f"Missing required fields in SessionMetadata: sessionId={sessionId}, "
- f"startTime={startTime}, modifiedTime={modifiedTime}, isRemote={isRemote}"
+ f"Missing required fields in SessionMetadata: sessionId={session_id}, "
+ f"startTime={start_time}, modifiedTime={modified_time}, isRemote={is_remote}"
)
summary = obj.get("summary")
context_dict = obj.get("context")
context = SessionContext.from_dict(context_dict) if context_dict else None
return SessionMetadata(
- sessionId=str(sessionId),
- startTime=str(startTime),
- modifiedTime=str(modifiedTime),
- isRemote=bool(isRemote),
+ session_id=str(session_id),
+ start_time=_parse_session_timestamp(start_time),
+ modified_time=_parse_session_timestamp(modified_time),
+ is_remote=bool(is_remote),
summary=summary,
context=context,
)
def to_dict(self) -> dict:
result: dict = {}
- result["sessionId"] = self.sessionId
- result["startTime"] = self.startTime
- result["modifiedTime"] = self.modifiedTime
- result["isRemote"] = self.isRemote
+ result["sessionId"] = self.session_id
+ result["startTime"] = self.start_time.isoformat()
+ result["modifiedTime"] = self.modified_time.isoformat()
+ result["isRemote"] = self.is_remote
if self.summary is not None:
result["summary"] = self.summary
if self.context is not None:
@@ -776,6 +815,18 @@ def to_dict(self) -> dict:
return result
+def _parse_session_timestamp(value: Any) -> datetime:
+ """Parse a wire-format timestamp into ``datetime``.
+
+ Accepts either an ISO-8601 string (server-sent JSON) or an existing
+ ``datetime`` (round-tripped from a previous parse). Returns the value
+ as-is if it's already a ``datetime``.
+ """
+ if isinstance(value, datetime):
+ return value
+ return from_datetime(value)
+
+
# ============================================================================
# Session Lifecycle Types (for TUI+server mode)
# ============================================================================
@@ -793,50 +844,107 @@ def to_dict(self) -> dict:
class SessionLifecycleEventMetadata:
"""Metadata for session lifecycle events."""
- startTime: str
- modifiedTime: str
+ start_time: datetime
+ modified_time: datetime
summary: str | None = None
@staticmethod
def from_dict(data: dict) -> SessionLifecycleEventMetadata:
return SessionLifecycleEventMetadata(
- startTime=data.get("startTime", ""),
- modifiedTime=data.get("modifiedTime", ""),
+ start_time=_parse_session_timestamp(data.get("startTime", "")),
+ modified_time=_parse_session_timestamp(data.get("modifiedTime", "")),
summary=data.get("summary"),
)
@dataclass
-class SessionLifecycleEvent:
- """Session lifecycle event notification."""
+class SessionLifecycleEventBase:
+ """Base for session lifecycle event variants.
- type: SessionLifecycleEventType
- sessionId: str
+ Construct concrete variants directly (e.g. :class:`SessionCreatedEvent`,
+ :class:`SessionDeletedEvent`); pattern-match on the variant class to
+ branch on the event kind.
+ """
+
+ session_id: str
metadata: SessionLifecycleEventMetadata | None = None
- @staticmethod
- def from_dict(data: dict) -> SessionLifecycleEvent:
- metadata = None
- if "metadata" in data and data["metadata"]:
- metadata = SessionLifecycleEventMetadata.from_dict(data["metadata"])
- return SessionLifecycleEvent(
- type=data.get("type", "session.updated"),
- sessionId=data.get("sessionId", ""),
- metadata=metadata,
- )
+
+@dataclass
+class SessionCreatedEvent(SessionLifecycleEventBase):
+ """Emitted when a session is created."""
+
+ type: ClassVar[Literal["session.created"]] = "session.created"
+
+
+@dataclass
+class SessionDeletedEvent(SessionLifecycleEventBase):
+ """Emitted when a session is deleted."""
+
+ type: ClassVar[Literal["session.deleted"]] = "session.deleted"
+
+
+@dataclass
+class SessionUpdatedEvent(SessionLifecycleEventBase):
+ """Emitted when a session is updated (summary/title/etc. changed)."""
+
+ type: ClassVar[Literal["session.updated"]] = "session.updated"
+
+
+@dataclass
+class SessionForegroundEvent(SessionLifecycleEventBase):
+ """Emitted when a session moves to the foreground (TUI+server mode)."""
+
+ type: ClassVar[Literal["session.foreground"]] = "session.foreground"
+
+
+@dataclass
+class SessionBackgroundEvent(SessionLifecycleEventBase):
+ """Emitted when a session moves to the background (TUI+server mode)."""
+
+ type: ClassVar[Literal["session.background"]] = "session.background"
+
+
+SessionLifecycleEvent = (
+ SessionCreatedEvent
+ | SessionDeletedEvent
+ | SessionUpdatedEvent
+ | SessionForegroundEvent
+ | SessionBackgroundEvent
+)
+
+
+def _session_lifecycle_event_from_dict(data: dict) -> SessionLifecycleEvent:
+ """Construct the correct :class:`SessionLifecycleEvent` variant from a wire dict."""
+ metadata = None
+ if "metadata" in data and data["metadata"]:
+ metadata = SessionLifecycleEventMetadata.from_dict(data["metadata"])
+ session_id = data.get("sessionId", "")
+ event_type = data.get("type")
+ if event_type == "session.created":
+ return SessionCreatedEvent(session_id=session_id, metadata=metadata)
+ if event_type == "session.deleted":
+ return SessionDeletedEvent(session_id=session_id, metadata=metadata)
+ if event_type == "session.foreground":
+ return SessionForegroundEvent(session_id=session_id, metadata=metadata)
+ if event_type == "session.background":
+ return SessionBackgroundEvent(session_id=session_id, metadata=metadata)
+ # Default to ``session.updated`` for unknown event types so consumers
+ # keep working across server upgrades.
+ return SessionUpdatedEvent(session_id=session_id, metadata=metadata)
SessionLifecycleHandler = Callable[[SessionLifecycleEvent], None]
HandlerUnsubcribe = Callable[[], None]
-NO_RESULT_PERMISSION_V2_ERROR = (
+_NO_RESULT_PERMISSION_V2_ERROR = (
"Permission handlers cannot return 'no-result' when connected to a protocol v2 server."
)
# Minimum protocol version this SDK can communicate with.
# Servers reporting a version below this are rejected.
-MIN_PROTOCOL_VERSION = 2
+_MIN_PROTOCOL_VERSION = 2
def _get_bundled_cli_path() -> str | None:
@@ -923,99 +1031,160 @@ class CopilotClient:
>>> await client.stop()
>>> # Or connect to an existing server
- >>> client = CopilotClient(ExternalServerConfig(url="localhost:3000"))
+ >>> client = CopilotClient(
+ ... connection=RuntimeConnection.for_uri("localhost:3000"),
+ ... )
"""
def __init__(
self,
- config: SubprocessConfig | ExternalServerConfig | None = None,
*,
- auto_start: bool = True,
+ connection: RuntimeConnection | None = None,
+ working_directory: str | None = None,
+ log_level: LogLevel = "info",
+ env: dict[str, str] | None = None,
+ github_token: str | None = None,
+ base_directory: str | None = None,
+ use_logged_in_user: bool | None = None,
+ telemetry: TelemetryConfig | None = None,
+ session_fs: SessionFsConfig | None = None,
+ session_idle_timeout_seconds: int | None = None,
+ enable_remote_sessions: bool = False,
on_list_models: Callable[[], list[ModelInfo] | Awaitable[list[ModelInfo]]] | None = None,
):
"""
Initialize a new CopilotClient.
+ All process-management options (``working_directory``, ``log_level``,
+ ``env``, ``github_token``, …) apply only when the SDK spawns the runtime
+ (stdio / tcp connections). They are ignored when connecting to an
+ existing runtime via :meth:`RuntimeConnection.for_uri`.
+
Args:
- config: Connection configuration. Pass a :class:`SubprocessConfig` to
- spawn a local CLI process, or an :class:`ExternalServerConfig` to
- connect to an existing server. Defaults to ``SubprocessConfig()``.
- auto_start: Automatically start the connection on first use
- (default: ``True``).
- on_list_models: Custom handler for :meth:`list_models`. When provided,
- the handler is called instead of querying the CLI server.
+ connection: How to reach the runtime. Defaults to
+ :meth:`RuntimeConnection.for_stdio` with the bundled binary.
+ working_directory: Working directory for the runtime process.
+ ``None`` uses the current directory.
+ log_level: Log level for the runtime process. Defaults to ``"info"``.
+ env: Environment variables for the runtime process. ``None`` inherits
+ the current env.
+ github_token: GitHub token for authentication. Takes priority over
+ other auth methods.
+ base_directory: Base directory for Copilot data (session state,
+ config, etc.). Sets the ``COPILOT_HOME`` environment variable on
+ the spawned runtime. When ``None``, the runtime defaults to
+ ``~/.copilot``.
+ use_logged_in_user: Use the logged-in user for authentication.
+ ``None`` (default) resolves to ``True`` unless ``github_token``
+ is set.
+ telemetry: OpenTelemetry configuration. Providing this enables
+ telemetry.
+ session_fs: Connection-level session filesystem provider
+ configuration.
+ session_idle_timeout_seconds: Server-wide session idle timeout in
+ seconds. Sessions without activity for this duration are
+ automatically cleaned up. Set to ``None`` or ``0`` to disable.
+ enable_remote_sessions: Enable remote session support (Mission
+ Control integration). When ``True``, sessions in a GitHub
+ repository working directory are accessible from GitHub web
+ and mobile.
+ on_list_models: Custom handler for :meth:`list_models`. When
+ provided, the handler is called instead of querying the runtime
+ server.
Example:
- >>> # Default — spawns CLI server using stdio
+ >>> # Default — spawns runtime using stdio with the bundled binary
>>> client = CopilotClient()
>>>
- >>> # Connect to an existing server
- >>> client = CopilotClient(ExternalServerConfig(url="localhost:3000"))
+ >>> # Connect to an existing runtime
+ >>> client = CopilotClient(
+ ... connection=RuntimeConnection.for_uri("localhost:3000"),
+ ... )
>>>
- >>> # Custom CLI path with specific log level
+ >>> # Custom runtime path with specific log level
>>> client = CopilotClient(
- ... SubprocessConfig(
- ... cli_path="/usr/local/bin/copilot",
- ... log_level="debug",
- ... )
+ ... connection=RuntimeConnection.for_stdio(path="/usr/local/bin/copilot"),
+ ... log_level="debug",
... )
"""
- if config is None:
- config = SubprocessConfig()
+ options = _CopilotClientOptions(
+ connection=connection,
+ working_directory=working_directory,
+ log_level=log_level,
+ env=env,
+ github_token=github_token,
+ base_directory=base_directory,
+ use_logged_in_user=use_logged_in_user,
+ telemetry=telemetry,
+ session_fs=session_fs,
+ session_idle_timeout_seconds=session_idle_timeout_seconds,
+ enable_remote_sessions=enable_remote_sessions,
+ on_list_models=on_list_models,
+ )
+ connection = (
+ options.connection if options.connection is not None else RuntimeConnection.for_stdio()
+ )
- self._config: SubprocessConfig | ExternalServerConfig = config
- self._auto_start = auto_start
- self._on_list_models = on_list_models
+ self._options: _CopilotClientOptions = options
+ self._connection: RuntimeConnection = connection
+ self._on_list_models = options.on_list_models
- # Resolve connection-mode-specific state
+ # Resolve connection-mode-specific state.
self._actual_host: str = "localhost"
- self._is_external_server: bool = isinstance(config, ExternalServerConfig)
-
- if config.tcp_connection_token is not None and len(config.tcp_connection_token) == 0:
- raise ValueError("tcp_connection_token must be a non-empty string")
-
- if isinstance(config, ExternalServerConfig):
- self._actual_host, actual_port = self._parse_cli_url(config.url)
- self._actual_port: int | None = actual_port
- self._effective_connection_token: str | None = config.tcp_connection_token
+ self._is_external_server: bool = isinstance(connection, UriRuntimeConnection)
+
+ if isinstance(connection, UriRuntimeConnection):
+ if connection.connection_token is not None and len(connection.connection_token) == 0:
+ raise ValueError("connection_token must be a non-empty string")
+ self._actual_host, actual_port = self._parse_cli_url(connection.url)
+ self._runtime_port: int | None = actual_port
+ self._effective_connection_token: str | None = connection.connection_token
else:
- self._actual_port = None
-
- if config.tcp_connection_token is not None and config.use_stdio:
- raise ValueError("tcp_connection_token cannot be used with use_stdio=True")
- if config.use_stdio:
- self._effective_connection_token = None
- elif config.tcp_connection_token is not None:
- self._effective_connection_token = config.tcp_connection_token
+ assert isinstance(connection, ChildProcessRuntimeConnection)
+ self._runtime_port = None
+
+ if isinstance(connection, TcpRuntimeConnection):
+ if (
+ connection.connection_token is not None
+ and len(connection.connection_token) == 0
+ ):
+ raise ValueError("connection_token must be a non-empty string")
+ self._effective_connection_token = (
+ connection.connection_token
+ if connection.connection_token is not None
+ else str(uuid.uuid4())
+ )
else:
- self._effective_connection_token = str(uuid.uuid4())
+ self._effective_connection_token = None
- # Resolve CLI path: explicit > COPILOT_CLI_PATH env var > bundled binary
- effective_env = config.env if config.env is not None else os.environ
+ # Resolve CLI path: explicit > COPILOT_CLI_PATH env var > bundled binary.
+ effective_env = options.env if options.env is not None else os.environ
self._cli_path_source: str | None = "explicit"
- if config.cli_path is None:
+ if connection.path is None:
env_cli_path = effective_env.get("COPILOT_CLI_PATH")
if env_cli_path:
- config.cli_path = env_cli_path
+ connection.path = env_cli_path
self._cli_path_source = "environment"
else:
bundled_path = _get_bundled_cli_path()
if bundled_path:
- config.cli_path = bundled_path
+ connection.path = bundled_path
self._cli_path_source = "bundled"
else:
raise RuntimeError(
"Copilot CLI not found. The bundled CLI binary is not available. "
- "Ensure you installed a platform-specific wheel, or provide cli_path."
+ "Ensure you installed a platform-specific wheel, or set "
+ "RuntimeConnection.for_stdio(path=...) / "
+ "RuntimeConnection.for_tcp(path=...)."
)
# Resolve use_logged_in_user default
- if config.use_logged_in_user is None:
- config.use_logged_in_user = not bool(config.github_token)
+ if options.use_logged_in_user is None:
+ options.use_logged_in_user = not bool(options.github_token)
self._process: subprocess.Popen | None = None
self._client: JsonRpcClient | None = None
- self._state: ConnectionState = "disconnected"
+ self._state: _ConnectionState = "disconnected"
self._sessions: dict[str, CopilotSession] = {}
self._sessions_lock = threading.Lock()
self._models_cache: list[ModelInfo] | None = None
@@ -1027,9 +1196,9 @@ def __init__(
self._lifecycle_handlers_lock = threading.Lock()
self._rpc: ServerRpc | None = None
self._negotiated_protocol_version: int | None = None
- if config.session_fs is not None:
- _validate_session_fs_config(config.session_fs)
- self._session_fs_config = config.session_fs
+ if options.session_fs is not None:
+ _validate_session_fs_config(options.session_fs)
+ self._session_fs_config = options.session_fs
@property
def rpc(self) -> ServerRpc:
@@ -1039,14 +1208,14 @@ def rpc(self) -> ServerRpc:
return self._rpc
@property
- def actual_port(self) -> int | None:
- """The actual TCP port the CLI server is listening on, if using TCP transport.
+ def runtime_port(self) -> int | None:
+ """TCP port the runtime is listening on, when using TCP transport.
Useful for multi-client scenarios where a second client needs to connect
- to the same server. Only available after :meth:`start` completes and
+ to the same runtime. Only available after :meth:`start` completes and
only when not using stdio transport.
"""
- return self._actual_port
+ return self._runtime_port
def _parse_cli_url(self, url: str) -> tuple[str, int]:
"""
@@ -1128,18 +1297,18 @@ async def start(self) -> None:
"""
Start the CLI server and establish a connection.
- If connecting to an external server (via :class:`ExternalServerConfig`),
+ If connecting to an already-running runtime (via :meth:`RuntimeConnection.for_uri`),
only establishes the connection. Otherwise, spawns the CLI server process
and then connects.
- This method is called automatically when creating a session if ``auto_start``
- is True (default).
+ This method is called automatically when creating a session, so most
+ callers do not need to call it explicitly.
Raises:
RuntimeError: If the server fails to start or the connection fails.
Example:
- >>> client = CopilotClient(auto_start=False)
+ >>> client = CopilotClient()
>>> await client.start()
>>> # Now ready to create sessions
"""
@@ -1285,7 +1454,7 @@ async def stop(self) -> None:
self._state = "disconnected"
if not self._is_external_server:
- self._actual_port = None
+ self._runtime_port = None
if errors:
raise ExceptionGroup("errors during CopilotClient.stop()", errors)
@@ -1340,7 +1509,7 @@ async def force_stop(self) -> None:
self._state = "disconnected"
if not self._is_external_server:
- self._actual_port = None
+ self._runtime_port = None
async def create_session(
self,
@@ -1375,8 +1544,8 @@ async def create_session(
on_event: Callable[[SessionEvent], None] | None = None,
commands: list[CommandDefinition] | None = None,
on_elicitation_request: ElicitationHandler | None = None,
- on_exit_plan_mode: ExitPlanModeHandler | None = None,
- on_auto_mode_switch: AutoModeSwitchHandler | None = None,
+ on_exit_plan_mode_request: ExitPlanModeHandler | None = None,
+ on_auto_mode_switch_request: AutoModeSwitchHandler | None = None,
create_session_fs_handler: CreateSessionFsHandler | None = None,
github_token: str | None = None,
remote_session: RemoteSessionMode | None = None,
@@ -1386,8 +1555,8 @@ async def create_session(
Create a new conversation session with the Copilot CLI.
Sessions maintain conversation state, handle events, and manage tool execution.
- If the client is not connected and ``auto_start`` is enabled, this will
- automatically start the connection.
+ If the client is not yet connected, this will automatically start the
+ connection.
Args:
on_permission_request: Optional handler for permission requests. When
@@ -1452,7 +1621,6 @@ async def create_session(
A :class:`CopilotSession` instance for the new session.
Raises:
- RuntimeError: If the client is not connected and auto_start is disabled.
ValueError: If ``on_permission_request`` is provided but not callable.
Example:
@@ -1470,10 +1638,7 @@ async def create_session(
if on_permission_request is not None and not callable(on_permission_request):
raise ValueError("on_permission_request must be callable when provided.")
if not self._client:
- if self._auto_start:
- await self.start()
- else:
- raise RuntimeError("Client not connected. Call start() first.")
+ await self.start()
tool_defs = []
if tools:
@@ -1518,8 +1683,8 @@ async def create_session(
# Enable elicitation request callback if handler provided
payload["requestElicitation"] = bool(on_elicitation_request)
- payload["requestExitPlanMode"] = bool(on_exit_plan_mode)
- payload["requestAutoModeSwitch"] = bool(on_auto_mode_switch)
+ payload["requestExitPlanMode"] = bool(on_exit_plan_mode_request)
+ payload["requestAutoModeSwitch"] = bool(on_auto_mode_switch_request)
# Serialize commands (name + description only) into payload
if commands:
@@ -1662,10 +1827,10 @@ async def create_session(
session._register_user_input_handler(on_user_input_request)
if on_elicitation_request:
session._register_elicitation_handler(on_elicitation_request)
- if on_exit_plan_mode:
- session._register_exit_plan_mode_handler(on_exit_plan_mode)
- if on_auto_mode_switch:
- session._register_auto_mode_switch_handler(on_auto_mode_switch)
+ if on_exit_plan_mode_request:
+ session._register_exit_plan_mode_handler(on_exit_plan_mode_request)
+ if on_auto_mode_switch_request:
+ session._register_auto_mode_switch_handler(on_auto_mode_switch_request)
if hooks:
session._register_hooks(hooks)
if transform_callbacks:
@@ -1754,8 +1919,8 @@ async def resume_session(
on_event: Callable[[SessionEvent], None] | None = None,
commands: list[CommandDefinition] | None = None,
on_elicitation_request: ElicitationHandler | None = None,
- on_exit_plan_mode: ExitPlanModeHandler | None = None,
- on_auto_mode_switch: AutoModeSwitchHandler | None = None,
+ on_exit_plan_mode_request: ExitPlanModeHandler | None = None,
+ on_auto_mode_switch_request: AutoModeSwitchHandler | None = None,
create_session_fs_handler: CreateSessionFsHandler | None = None,
github_token: str | None = None,
remote_session: RemoteSessionMode | None = None,
@@ -1851,10 +2016,7 @@ async def resume_session(
if on_permission_request is not None and not callable(on_permission_request):
raise ValueError("on_permission_request must be callable when provided.")
if not self._client:
- if self._auto_start:
- await self.start()
- else:
- raise RuntimeError("Client not connected. Call start() first.")
+ await self.start()
tool_defs = []
if tools:
@@ -1912,8 +2074,8 @@ async def resume_session(
# Enable elicitation request callback if handler provided
payload["requestElicitation"] = bool(on_elicitation_request)
- payload["requestExitPlanMode"] = bool(on_exit_plan_mode)
- payload["requestAutoModeSwitch"] = bool(on_auto_mode_switch)
+ payload["requestExitPlanMode"] = bool(on_exit_plan_mode_request)
+ payload["requestAutoModeSwitch"] = bool(on_auto_mode_switch_request)
# Serialize commands (name + description only) into payload
if commands:
@@ -2015,10 +2177,10 @@ async def resume_session(
session._register_user_input_handler(on_user_input_request)
if on_elicitation_request:
session._register_elicitation_handler(on_elicitation_request)
- if on_exit_plan_mode:
- session._register_exit_plan_mode_handler(on_exit_plan_mode)
- if on_auto_mode_switch:
- session._register_auto_mode_switch_handler(on_auto_mode_switch)
+ if on_exit_plan_mode_request:
+ session._register_exit_plan_mode_handler(on_exit_plan_mode_request)
+ if on_auto_mode_switch_request:
+ session._register_auto_mode_switch_handler(on_auto_mode_switch_request)
if hooks:
session._register_hooks(hooks)
if transform_callbacks:
@@ -2074,20 +2236,6 @@ async def resume_session(
)
return session
- def get_state(self) -> ConnectionState:
- """
- Get the current connection state of the client.
-
- Returns:
- The current connection state: "disconnected", "connecting",
- "connected", or "error".
-
- Example:
- >>> if client.get_state() == "connected":
- ... session = await client.create_session()
- """
- return self._state
-
async def ping(self, message: str | None = None) -> PingResponse:
"""
Send a ping request to the server to verify connectivity.
@@ -2256,7 +2404,7 @@ async def get_session_metadata(self, session_id: str) -> SessionMetadata | None:
Example:
>>> metadata = await client.get_session_metadata("session-123")
>>> if metadata:
- ... print(f"Session started at: {metadata.startTime}")
+ ... print(f"Session started at: {metadata.start_time}")
"""
if not self._client:
raise RuntimeError("Client not connected")
@@ -2376,14 +2524,16 @@ async def set_foreground_session_id(self, session_id: str) -> None:
raise RuntimeError(f"Failed to set foreground session: {error}")
@overload
- def on(self, handler: SessionLifecycleHandler, /) -> HandlerUnsubcribe: ...
+ def on_lifecycle(self, handler: SessionLifecycleHandler, /) -> HandlerUnsubcribe:
+ pass
@overload
- def on(
+ def on_lifecycle(
self, event_type: SessionLifecycleEventType, /, handler: SessionLifecycleHandler
- ) -> HandlerUnsubcribe: ...
+ ) -> HandlerUnsubcribe:
+ pass
- def on(
+ def on_lifecycle(
self,
event_type_or_handler: SessionLifecycleEventType | SessionLifecycleHandler,
/,
@@ -2396,8 +2546,8 @@ def on(
or change foreground/background state (in TUI+server mode).
Can be called in two ways:
- - on(handler): Subscribe to all lifecycle events
- - on(event_type, handler): Subscribe to a specific event type
+ - on_lifecycle(handler): Subscribe to all lifecycle events
+ - on_lifecycle(event_type, handler): Subscribe to a specific event type
Args:
event_type_or_handler: Either a specific event type to listen for,
@@ -2409,10 +2559,12 @@ def on(
Example:
>>> # Subscribe to specific event type
- >>> unsubscribe = client.on("session.foreground", lambda e: print(e.sessionId))
+ >>> unsubscribe = client.on_lifecycle(
+ ... "session.foreground", lambda e: print(e.session_id)
+ ... )
>>>
>>> # Subscribe to all events
- >>> unsubscribe = client.on(lambda e: print(f"{e.type}: {e.sessionId}"))
+ >>> unsubscribe = client.on_lifecycle(lambda e: print(f"{e.type}: {e.session_id}"))
>>>
>>> # Later, to stop receiving events:
>>> unsubscribe()
@@ -2444,7 +2596,10 @@ def unsubscribe_typed() -> None:
return unsubscribe_typed
else:
- raise ValueError("Invalid arguments: use on(handler) or on(event_type, handler)")
+ raise ValueError(
+ "Invalid arguments: use on_lifecycle(handler) "
+ "or on_lifecycle(event_type, handler)"
+ )
def _dispatch_lifecycle_event(self, event: SessionLifecycleEvent) -> None:
"""Dispatch a lifecycle event to all registered handlers."""
@@ -2489,22 +2644,22 @@ async def _verify_protocol_version(self) -> None:
# is silently dropped — the legacy server can't enforce one.
used_fallback_ping = True
ping_result = await self.ping()
- server_version = ping_result.protocolVersion
+ server_version = ping_result.protocol_version
else:
raise
if server_version is None:
raise RuntimeError(
"SDK protocol version mismatch: "
- f"SDK supports versions {MIN_PROTOCOL_VERSION}-{max_version}"
+ f"SDK supports versions {_MIN_PROTOCOL_VERSION}-{max_version}"
", but server does not report a protocol version. "
"Please update your server to ensure compatibility."
)
- if server_version < MIN_PROTOCOL_VERSION or server_version > max_version:
+ if server_version < _MIN_PROTOCOL_VERSION or server_version > max_version:
raise RuntimeError(
"SDK protocol version mismatch: "
- f"SDK supports versions {MIN_PROTOCOL_VERSION}-{max_version}"
+ f"SDK supports versions {_MIN_PROTOCOL_VERSION}-{max_version}"
f", but server reports version {server_version}. "
"Please update your SDK or server to ensure compatibility."
)
@@ -2546,8 +2701,8 @@ def _convert_provider_to_wire_format(
wire_provider["modelId"] = provider["model_id"]
if "wire_model" in provider:
wire_provider["wireModel"] = provider["wire_model"]
- if "max_input_tokens" in provider:
- wire_provider["maxPromptTokens"] = provider["max_input_tokens"]
+ if "max_prompt_tokens" in provider:
+ wire_provider["maxPromptTokens"] = provider["max_prompt_tokens"]
if "max_output_tokens" in provider:
wire_provider["maxOutputTokens"] = provider["max_output_tokens"]
if "azure" in provider:
@@ -2606,19 +2761,21 @@ def _convert_default_agent_to_wire_format(
return wire
async def _start_cli_server(self) -> None:
- """
- Start the CLI server process.
+ """Start the runtime process.
- This spawns the CLI server as a subprocess using the configured transport
+ This spawns the runtime as a subprocess using the configured transport
mode (stdio or TCP).
Raises:
RuntimeError: If the server fails to start or times out.
"""
- assert isinstance(self._config, SubprocessConfig)
- cfg = self._config
+ assert isinstance(self._connection, ChildProcessRuntimeConnection)
+ conn = self._connection
+ opts = self._options
+ use_stdio = isinstance(conn, StdioRuntimeConnection)
+ tcp_port = conn.port if isinstance(conn, TcpRuntimeConnection) else 0
- cli_path = cfg.cli_path
+ cli_path = conn.path
assert cli_path is not None # resolved in __init__
# Verify CLI exists
@@ -2627,24 +2784,24 @@ async def _start_cli_server(self) -> None:
if (cli_path := shutil.which(cli_path)) is None:
raise RuntimeError(f"Copilot CLI not found at {original_path}")
- # Start with user-provided cli_args, then add SDK-managed args
- args = list(cfg.cli_args) + [
+ # Start with user-provided args, then add SDK-managed args
+ args = list(conn.args) + [
"--headless",
"--no-auto-update",
"--log-level",
- cfg.log_level,
+ opts.log_level,
]
# Add auth-related flags
- if cfg.github_token:
+ if opts.github_token:
args.extend(["--auth-token-env", "COPILOT_SDK_AUTH_TOKEN"])
- if not cfg.use_logged_in_user:
+ if not opts.use_logged_in_user:
args.append("--no-auto-login")
- if cfg.session_idle_timeout_seconds is not None and cfg.session_idle_timeout_seconds > 0:
- args.extend(["--session-idle-timeout", str(cfg.session_idle_timeout_seconds)])
+ if opts.session_idle_timeout_seconds is not None and opts.session_idle_timeout_seconds > 0:
+ args.extend(["--session-idle-timeout", str(opts.session_idle_timeout_seconds)])
- if cfg.remote:
+ if opts.enable_remote_sessions:
args.append("--remote")
# If cli_path is a .js file, run it with node
@@ -2659,28 +2816,28 @@ async def _start_cli_server(self) -> None:
"cli_path": cli_path,
"executable": args[0],
"cli_path_source": self._cli_path_source,
- "use_stdio": cfg.use_stdio,
- "port": None if cfg.use_stdio else cfg.port,
+ "use_stdio": use_stdio,
+ "port": None if use_stdio else tcp_port,
},
)
# Get environment variables
- if cfg.env is None:
+ if opts.env is None:
env = dict(os.environ)
else:
- env = dict(cfg.env)
+ env = dict(opts.env)
# Set auth token in environment if provided
- if cfg.github_token:
- env["COPILOT_SDK_AUTH_TOKEN"] = cfg.github_token
+ if opts.github_token:
+ env["COPILOT_SDK_AUTH_TOKEN"] = opts.github_token
if self._effective_connection_token:
env["COPILOT_CONNECTION_TOKEN"] = self._effective_connection_token
- if cfg.copilot_home:
- env["COPILOT_HOME"] = cfg.copilot_home
+ if opts.base_directory:
+ env["COPILOT_HOME"] = opts.base_directory
# Set OpenTelemetry environment variables if telemetry config is provided
- telemetry = cfg.telemetry
+ telemetry = opts.telemetry
if telemetry is not None:
env["COPILOT_OTEL_ENABLED"] = "true"
if "otlp_endpoint" in telemetry:
@@ -2699,11 +2856,11 @@ async def _start_cli_server(self) -> None:
# On Windows, hide the console window to avoid distracting users in GUI apps
creationflags = subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0
- cwd = cfg.working_directory or os.getcwd()
+ cwd = opts.working_directory or os.getcwd()
# Choose transport mode
spawn_start = time.perf_counter()
- if cfg.use_stdio:
+ if use_stdio:
args.append("--stdio")
# Use regular Popen with pipes (buffering=0 for unbuffered)
self._process = subprocess.Popen(
@@ -2717,8 +2874,8 @@ async def _start_cli_server(self) -> None:
creationflags=creationflags,
)
else:
- if cfg.port > 0:
- args.extend(["--port", str(cfg.port)])
+ if tcp_port > 0:
+ args.extend(["--port", str(tcp_port)])
self._process = subprocess.Popen(
args,
stdin=subprocess.DEVNULL,
@@ -2736,7 +2893,7 @@ async def _start_cli_server(self) -> None:
)
# For stdio mode, we're ready immediately
- if cfg.use_stdio:
+ if use_stdio:
return
# For TCP mode, wait for port announcement
@@ -2755,7 +2912,7 @@ async def read_port():
logger.debug("[CLI] %s", line_str.rstrip())
match = re.search(r"listening on port (\d+)", line_str, re.IGNORECASE)
if match:
- self._actual_port = int(match.group(1))
+ self._runtime_port = int(match.group(1))
return
try:
@@ -2766,14 +2923,13 @@ async def read_port():
logging.DEBUG,
"CopilotClient._start_cli_server TCP port wait complete",
port_wait_start,
- port=self._actual_port,
+ port=self._runtime_port,
)
except TimeoutError:
raise RuntimeError("Timeout waiting for CLI server to start")
async def _connect_to_server(self) -> None:
- """
- Connect to the CLI server via the configured transport.
+ """Connect to the runtime via the configured transport.
Uses either stdio or TCP based on the client configuration.
@@ -2781,8 +2937,7 @@ async def _connect_to_server(self) -> None:
RuntimeError: If the connection fails.
"""
setup_start = time.perf_counter()
- use_stdio = isinstance(self._config, SubprocessConfig) and self._config.use_stdio
- if use_stdio:
+ if isinstance(self._connection, StdioRuntimeConnection):
await self._connect_via_stdio()
else:
await self._connect_via_tcp()
@@ -2824,7 +2979,7 @@ def handle_notification(method: str, params: dict):
session._dispatch_event(event)
elif method == "session.lifecycle":
# Handle session lifecycle events
- lifecycle_event = SessionLifecycleEvent.from_dict(params)
+ lifecycle_event = _session_lifecycle_event_from_dict(params)
self._dispatch_lifecycle_event(lifecycle_event)
self._client.set_notification_handler(handle_notification)
@@ -2860,7 +3015,7 @@ async def _connect_via_tcp(self) -> None:
Raises:
RuntimeError: If the server port is not available or connection fails.
"""
- if not self._actual_port:
+ if not self._runtime_port:
raise RuntimeError("Server port not available")
# Create a TCP socket connection with timeout
@@ -2876,9 +3031,9 @@ async def _connect_via_tcp(self) -> None:
tcp_connect_start = time.perf_counter()
logger.info(
"CopilotClient._connect_via_tcp connecting to CLI server",
- extra={"host": self._actual_host, "port": self._actual_port},
+ extra={"host": self._actual_host, "port": self._runtime_port},
)
- sock.connect((self._actual_host, self._actual_port))
+ sock.connect((self._actual_host, self._runtime_port))
sock.settimeout(None) # Remove timeout after connection
log_timing(
logger,
@@ -2886,11 +3041,11 @@ async def _connect_via_tcp(self) -> None:
"CopilotClient._connect_via_tcp TCP connect complete",
tcp_connect_start,
host=self._actual_host,
- port=self._actual_port,
+ port=self._runtime_port,
)
except OSError as e:
raise RuntimeError(
- f"Failed to connect to CLI server at {self._actual_host}:{self._actual_port}: {e}"
+ f"Failed to connect to CLI server at {self._actual_host}:{self._runtime_port}: {e}"
)
# Create a file-like wrapper for the socket
@@ -2949,7 +3104,7 @@ def handle_notification(method: str, params: dict):
session._dispatch_event(event)
elif method == "session.lifecycle":
# Handle session lifecycle events
- lifecycle_event = SessionLifecycleEvent.from_dict(params)
+ lifecycle_event = _session_lifecycle_event_from_dict(params)
self._dispatch_lifecycle_event(lifecycle_event)
self._client.set_notification_handler(handle_notification)
@@ -3193,22 +3348,14 @@ async def _handle_permission_request_v2(self, params: dict) -> dict:
raise ValueError(f"unknown session {session_id}")
try:
- perm_request = PermissionRequest.from_dict(permission_request)
+ perm_request = _load_PermissionRequest(permission_request)
result = await session._handle_permission_request(perm_request)
- if result.kind == "no-result":
- raise ValueError(NO_RESULT_PERMISSION_V2_ERROR)
- return {"result": {"kind": result.kind}}
+ if isinstance(result, PermissionNoResult):
+ raise ValueError(_NO_RESULT_PERMISSION_V2_ERROR)
+ return {"result": result.to_dict()}
except ValueError as exc:
- if str(exc) == NO_RESULT_PERMISSION_V2_ERROR:
+ if str(exc) == _NO_RESULT_PERMISSION_V2_ERROR:
raise
- return {
- "result": {
- "kind": "user-not-available",
- }
- }
+ return {"result": PermissionDecisionUserNotAvailable().to_dict()}
except Exception: # pylint: disable=broad-except
- return {
- "result": {
- "kind": "user-not-available",
- }
- }
+ return {"result": PermissionDecisionUserNotAvailable().to_dict()}
diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py
index ce9f98f2a..929aa79e6 100644
--- a/python/copilot/generated/rpc.py
+++ b/python/copilot/generated/rpc.py
@@ -2,8 +2,9 @@
AUTO-GENERATED FILE - DO NOT EDIT
Generated from: api.schema.json
"""
+from __future__ import annotations
-from typing import TYPE_CHECKING
+from typing import ClassVar, TYPE_CHECKING
from .session_events import AbortReason, EmbeddedBlobResourceContents, EmbeddedTextResourceContents, McpServerSource, McpServerStatus, PermissionPromptRequest, PermissionRule, ReasoningSummary, SessionEvent, SessionMode, ShutdownType, SkillSource, UserToolSessionApproval
@@ -384,6 +385,31 @@ def to_dict(self) -> dict:
result["includeSkills"] = from_union([from_bool, from_none], self.include_skills)
return result
+# Experimental: this type is part of an experimental API and may change or be removed.
+@dataclass
+class CommandsRespondToQueuedCommandRequest:
+ """Queued-command request ID and the result indicating whether the host executed it (and
+ whether to stop processing further queued commands).
+ """
+ request_id: str
+ """Request ID from the `command.queued` event the host is responding to."""
+
+ result: QueuedCommandResult
+ """Result of the queued command execution."""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'CommandsRespondToQueuedCommandRequest':
+ assert isinstance(obj, dict)
+ request_id = from_str(obj.get("requestId"))
+ result = _load_QueuedCommandResult(obj.get("result"))
+ return CommandsRespondToQueuedCommandRequest(request_id, result)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["requestId"] = from_str(self.request_id)
+ result["result"] = (self.result).to_dict()
+ return result
+
# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
class CommandsRespondToQueuedCommandResult:
@@ -2446,6 +2472,30 @@ class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind(Enum)
class PermissionDecisionRejectKind(Enum):
REJECT = "reject"
+# Experimental: this type is part of an experimental API and may change or be removed.
+@dataclass
+class PermissionDecisionRequest:
+ """Pending permission request ID and the decision to apply (approve/reject and scope)."""
+
+ request_id: str
+ """Request ID of the pending permission request"""
+
+ result: PermissionDecision
+ """The client's response to the pending permission prompt"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'PermissionDecisionRequest':
+ assert isinstance(obj, dict)
+ request_id = from_str(obj.get("requestId"))
+ result = _load_PermissionDecision(obj.get("result"))
+ return PermissionDecisionRequest(request_id, result)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["requestId"] = from_str(self.request_id)
+ result["result"] = (self.result).to_dict()
+ return result
+
class PermissionDecisionUserNotAvailableKind(Enum):
USER_NOT_AVAILABLE = "user-not-available"
@@ -3224,7 +3274,7 @@ def to_dict(self) -> dict:
class QueuedCommandHandled:
"""Schema for the `QueuedCommandHandled` type."""
- handled: bool
+ handled: ClassVar[str] = "true"
"""The host actually executed the queued command."""
stop_processing_queue: bool | None = None
@@ -3235,13 +3285,12 @@ class QueuedCommandHandled:
@staticmethod
def from_dict(obj: Any) -> 'QueuedCommandHandled':
assert isinstance(obj, dict)
- handled = from_bool(obj.get("handled"))
stop_processing_queue = from_union([from_bool, from_none], obj.get("stopProcessingQueue"))
- return QueuedCommandHandled(handled, stop_processing_queue)
+ return QueuedCommandHandled(stop_processing_queue)
def to_dict(self) -> dict:
result: dict = {}
- result["handled"] = from_bool(self.handled)
+ result["handled"] = self.handled
if self.stop_processing_queue is not None:
result["stopProcessingQueue"] = from_union([from_bool, from_none], self.stop_processing_queue)
return result
@@ -3251,7 +3300,7 @@ def to_dict(self) -> dict:
class QueuedCommandNotHandled:
"""Schema for the `QueuedCommandNotHandled` type."""
- handled: bool
+ handled: ClassVar[str] = "false"
"""The host did not execute the queued command. Unblocks the queue without claiming the
command was processed (e.g. when the handler threw before completing).
"""
@@ -3259,12 +3308,11 @@ class QueuedCommandNotHandled:
@staticmethod
def from_dict(obj: Any) -> 'QueuedCommandNotHandled':
assert isinstance(obj, dict)
- handled = from_bool(obj.get("handled"))
- return QueuedCommandNotHandled(handled)
+ return QueuedCommandNotHandled()
def to_dict(self) -> dict:
result: dict = {}
- result["handled"] = from_bool(self.handled)
+ result["handled"] = self.handled
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -4237,6 +4285,31 @@ def to_dict(self) -> dict:
result["skipped"] = from_list(from_str, self.skipped)
return result
+# Experimental: this type is part of an experimental API and may change or be removed.
+@dataclass
+class SessionSetCredentialsParams:
+ """New auth credentials to install on the session. Omit to leave credentials unchanged."""
+
+ credentials: AuthInfo | None = None
+ """The new auth credentials to install on the session. When omitted or `undefined`, the call
+ is a no-op and the session's existing credentials are preserved. The runtime stores the
+ value verbatim and uses it for outbound model/API requests; it does NOT re-validate or
+ re-fetch the associated Copilot user response. Several variants carry secret material;
+ treat this method's params as containing secrets at rest and in transit.
+ """
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'SessionSetCredentialsParams':
+ assert isinstance(obj, dict)
+ credentials = from_union([_load_AuthInfo, from_none], obj.get("credentials"))
+ return SessionSetCredentialsParams(credentials)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ if self.credentials is not None:
+ result["credentials"] = from_union([lambda x: (x).to_dict(), from_none], self.credentials)
+ return result
+
# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
class SessionSetCredentialsResult:
@@ -5196,6 +5269,25 @@ class TaskInfoType(Enum):
AGENT = "agent"
SHELL = "shell"
+# Experimental: this type is part of an experimental API and may change or be removed.
+@dataclass
+class TaskList:
+ """Background tasks currently tracked by the session."""
+
+ tasks: list[TaskInfo]
+ """Currently tracked tasks"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'TaskList':
+ assert isinstance(obj, dict)
+ tasks = from_list(_load_TaskInfo, obj.get("tasks"))
+ return TaskList(tasks)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["tasks"] = from_list(lambda x: (x).to_dict(), self.tasks)
+ return result
+
class TaskShellInfoType(Enum):
SHELL = "shell"
@@ -5237,6 +5329,29 @@ def to_dict(self) -> dict:
result["cancelled"] = from_bool(self.cancelled)
return result
+# Experimental: this type is part of an experimental API and may change or be removed.
+@dataclass
+class TasksGetCurrentPromotableResult:
+ """The first sync-waiting task that can currently be promoted to background mode."""
+
+ task: TaskInfo | None = None
+ """The first sync-waiting task (agent first, then shell) that can currently be promoted to
+ background mode. Omitted if no such task exists. The returned task is guaranteed to have
+ executionMode='sync' and canPromoteToBackground=true at the time of the call.
+ """
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'TasksGetCurrentPromotableResult':
+ assert isinstance(obj, dict)
+ task = from_union([_load_TaskInfo, from_none], obj.get("task"))
+ return TasksGetCurrentPromotableResult(task)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ if self.task is not None:
+ result["task"] = from_union([lambda x: (x).to_dict(), from_none], self.task)
+ return result
+
# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
class TasksGetProgressRequest:
@@ -5256,6 +5371,30 @@ def to_dict(self) -> dict:
result["id"] = from_str(self.id)
return result
+# Experimental: this type is part of an experimental API and may change or be removed.
+@dataclass
+class TasksPromoteCurrentToBackgroundResult:
+ """The promoted task as it now exists in background mode, omitted if no promotable task was
+ waiting.
+ """
+ task: TaskInfo | None = None
+ """The promoted task as it now exists in background mode, omitted if no promotable task was
+ waiting. Atomic operation: avoids the race window of getCurrentPromotable +
+ promoteToBackground.
+ """
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'TasksPromoteCurrentToBackgroundResult':
+ assert isinstance(obj, dict)
+ task = from_union([_load_TaskInfo, from_none], obj.get("task"))
+ return TasksPromoteCurrentToBackgroundResult(task)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ if self.task is not None:
+ result["task"] = from_union([lambda x: (x).to_dict(), from_none], self.task)
+ return result
+
# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
class TasksPromoteToBackgroundRequest:
@@ -6312,7 +6451,7 @@ class SendAttachmentDirectory:
path: str
"""Absolute directory path"""
- type: SlashCommandInputCompletion
+ type: ClassVar[str] = "directory"
"""Attachment type discriminator"""
@staticmethod
@@ -6320,14 +6459,13 @@ def from_dict(obj: Any) -> 'SendAttachmentDirectory':
assert isinstance(obj, dict)
display_name = from_str(obj.get("displayName"))
path = from_str(obj.get("path"))
- type = SlashCommandInputCompletion(obj.get("type"))
- return SendAttachmentDirectory(display_name, path, type)
+ return SendAttachmentDirectory(display_name, path)
def to_dict(self) -> dict:
result: dict = {}
result["displayName"] = from_str(self.display_name)
result["path"] = from_str(self.path)
- result["type"] = to_enum(SlashCommandInputCompletion, self.type)
+ result["type"] = self.type
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -6790,7 +6928,7 @@ class ExternalToolTextResultForLlmContentAudio:
mime_type: str
"""MIME type of the audio (e.g., audio/wav, audio/mpeg)"""
- type: ExternalToolTextResultForLlmContentAudioType
+ type: ClassVar[str] = "audio"
"""Content block type discriminator"""
@staticmethod
@@ -6798,14 +6936,13 @@ def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentAudio':
assert isinstance(obj, dict)
data = from_str(obj.get("data"))
mime_type = from_str(obj.get("mimeType"))
- type = ExternalToolTextResultForLlmContentAudioType(obj.get("type"))
- return ExternalToolTextResultForLlmContentAudio(data, mime_type, type)
+ return ExternalToolTextResultForLlmContentAudio(data, mime_type)
def to_dict(self) -> dict:
result: dict = {}
result["data"] = from_str(self.data)
result["mimeType"] = from_str(self.mime_type)
- result["type"] = to_enum(ExternalToolTextResultForLlmContentAudioType, self.type)
+ result["type"] = self.type
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -6819,7 +6956,7 @@ class ExternalToolTextResultForLlmContentImage:
mime_type: str
"""MIME type of the image (e.g., image/png, image/jpeg)"""
- type: ExternalToolTextResultForLlmContentImageType
+ type: ClassVar[str] = "image"
"""Content block type discriminator"""
@staticmethod
@@ -6827,14 +6964,13 @@ def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentImage':
assert isinstance(obj, dict)
data = from_str(obj.get("data"))
mime_type = from_str(obj.get("mimeType"))
- type = ExternalToolTextResultForLlmContentImageType(obj.get("type"))
- return ExternalToolTextResultForLlmContentImage(data, mime_type, type)
+ return ExternalToolTextResultForLlmContentImage(data, mime_type)
def to_dict(self) -> dict:
result: dict = {}
result["data"] = from_str(self.data)
result["mimeType"] = from_str(self.mime_type)
- result["type"] = to_enum(ExternalToolTextResultForLlmContentImageType, self.type)
+ result["type"] = self.type
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -6845,20 +6981,19 @@ class ExternalToolTextResultForLlmContentResource:
resource: ExternalToolTextResultForLlmContentResourceDetails
"""The embedded resource contents, either text or base64-encoded binary"""
- type: ExternalToolTextResultForLlmContentResourceType
+ type: ClassVar[str] = "resource"
"""Content block type discriminator"""
@staticmethod
def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentResource':
assert isinstance(obj, dict)
resource = (lambda x: from_union([EmbeddedTextResourceContents.from_dict, EmbeddedBlobResourceContents.from_dict], x))(obj.get("resource"))
- type = ExternalToolTextResultForLlmContentResourceType(obj.get("type"))
- return ExternalToolTextResultForLlmContentResource(resource, type)
+ return ExternalToolTextResultForLlmContentResource(resource)
def to_dict(self) -> dict:
result: dict = {}
result["resource"] = from_union([lambda x: to_class(EmbeddedTextResourceContents, x), lambda x: to_class(EmbeddedBlobResourceContents, x)], self.resource)
- result["type"] = to_enum(ExternalToolTextResultForLlmContentResourceType, self.type)
+ result["type"] = self.type
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -6869,7 +7004,7 @@ class ExternalToolTextResultForLlmContentTerminal:
text: str
"""Terminal/shell output text"""
- type: ExternalToolTextResultForLlmContentTerminalType
+ type: ClassVar[str] = "terminal"
"""Content block type discriminator"""
cwd: str | None = None
@@ -6882,15 +7017,14 @@ class ExternalToolTextResultForLlmContentTerminal:
def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentTerminal':
assert isinstance(obj, dict)
text = from_str(obj.get("text"))
- type = ExternalToolTextResultForLlmContentTerminalType(obj.get("type"))
cwd = from_union([from_str, from_none], obj.get("cwd"))
exit_code = from_union([from_int, from_none], obj.get("exitCode"))
- return ExternalToolTextResultForLlmContentTerminal(text, type, cwd, exit_code)
+ return ExternalToolTextResultForLlmContentTerminal(text, cwd, exit_code)
def to_dict(self) -> dict:
result: dict = {}
result["text"] = from_str(self.text)
- result["type"] = to_enum(ExternalToolTextResultForLlmContentTerminalType, self.type)
+ result["type"] = self.type
if self.cwd is not None:
result["cwd"] = from_union([from_str, from_none], self.cwd)
if self.exit_code is not None:
@@ -6905,20 +7039,19 @@ class ExternalToolTextResultForLlmContentText:
text: str
"""The text content"""
- type: KindEnum
+ type: ClassVar[str] = "text"
"""Content block type discriminator"""
@staticmethod
def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentText':
assert isinstance(obj, dict)
text = from_str(obj.get("text"))
- type = KindEnum(obj.get("type"))
- return ExternalToolTextResultForLlmContentText(text, type)
+ return ExternalToolTextResultForLlmContentText(text)
def to_dict(self) -> dict:
result: dict = {}
result["text"] = from_str(self.text)
- result["type"] = to_enum(KindEnum, self.type)
+ result["type"] = self.type
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -6926,7 +7059,7 @@ def to_dict(self) -> dict:
class SlashCommandTextResult:
"""Schema for the `SlashCommandTextResult` type."""
- kind: KindEnum
+ kind: ClassVar[str] = "text"
"""Text result discriminator"""
text: str
@@ -6946,16 +7079,15 @@ class SlashCommandTextResult:
@staticmethod
def from_dict(obj: Any) -> 'SlashCommandTextResult':
assert isinstance(obj, dict)
- kind = KindEnum(obj.get("kind"))
text = from_str(obj.get("text"))
markdown = from_union([from_bool, from_none], obj.get("markdown"))
preserve_ansi = from_union([from_bool, from_none], obj.get("preserveAnsi"))
runtime_settings_changed = from_union([from_bool, from_none], obj.get("runtimeSettingsChanged"))
- return SlashCommandTextResult(kind, text, markdown, preserve_ansi, runtime_settings_changed)
+ return SlashCommandTextResult(text, markdown, preserve_ansi, runtime_settings_changed)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(KindEnum, self.kind)
+ result["kind"] = self.kind
result["text"] = from_str(self.text)
if self.markdown is not None:
result["markdown"] = from_union([from_bool, from_none], self.markdown)
@@ -7977,97 +8109,99 @@ def to_dict(self) -> dict:
# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
-class PermissionDecisionApproveForLocationApprovalCommands:
- """Schema for the `PermissionDecisionApproveForLocationApprovalCommands` type."""
+class PermissionDecisionApproveForLocation:
+ """Schema for the `PermissionDecisionApproveForLocation` type."""
- command_identifiers: list[str]
- """Command identifiers covered by this approval."""
+ approval: PermissionDecisionApproveForLocationApproval
+ """Approval to persist for this location"""
- kind: PermissionDecisionApproveForLocationApprovalCommandsKind
- """Approval scoped to specific command identifiers."""
+ kind: ClassVar[str] = "approve-for-location"
+ """Approve and persist for this project location"""
+
+ location_key: str
+ """Location key (git root or cwd) to persist the approval to"""
@staticmethod
- def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalCommands':
+ def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocation':
assert isinstance(obj, dict)
- command_identifiers = from_list(from_str, obj.get("commandIdentifiers"))
- kind = PermissionDecisionApproveForLocationApprovalCommandsKind(obj.get("kind"))
- return PermissionDecisionApproveForLocationApprovalCommands(command_identifiers, kind)
+ approval = _load_PermissionDecisionApproveForLocationApproval(obj.get("approval"))
+ location_key = from_str(obj.get("locationKey"))
+ return PermissionDecisionApproveForLocation(approval, location_key)
def to_dict(self) -> dict:
result: dict = {}
- result["commandIdentifiers"] = from_list(from_str, self.command_identifiers)
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalCommandsKind, self.kind)
+ result["approval"] = (self.approval).to_dict()
+ result["kind"] = self.kind
+ result["locationKey"] = from_str(self.location_key)
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
-class PermissionDecisionApproveForSessionApprovalCommands:
- """Schema for the `PermissionDecisionApproveForSessionApprovalCommands` type."""
+class PermissionDecisionApproveForLocationApprovalCommands:
+ """Schema for the `PermissionDecisionApproveForLocationApprovalCommands` type."""
command_identifiers: list[str]
"""Command identifiers covered by this approval."""
- kind: PermissionDecisionApproveForLocationApprovalCommandsKind
+ kind: ClassVar[str] = "commands"
"""Approval scoped to specific command identifiers."""
@staticmethod
- def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalCommands':
+ def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalCommands':
assert isinstance(obj, dict)
command_identifiers = from_list(from_str, obj.get("commandIdentifiers"))
- kind = PermissionDecisionApproveForLocationApprovalCommandsKind(obj.get("kind"))
- return PermissionDecisionApproveForSessionApprovalCommands(command_identifiers, kind)
+ return PermissionDecisionApproveForLocationApprovalCommands(command_identifiers)
def to_dict(self) -> dict:
result: dict = {}
result["commandIdentifiers"] = from_list(from_str, self.command_identifiers)
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalCommandsKind, self.kind)
+ result["kind"] = self.kind
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
-class PermissionsLocationsAddToolApprovalDetailsCommands:
- """Schema for the `PermissionsLocationsAddToolApprovalDetailsCommands` type."""
+class PermissionDecisionApproveForSessionApprovalCommands:
+ """Schema for the `PermissionDecisionApproveForSessionApprovalCommands` type."""
command_identifiers: list[str]
"""Command identifiers covered by this approval."""
- kind: PermissionDecisionApproveForLocationApprovalCommandsKind
+ kind: ClassVar[str] = "commands"
"""Approval scoped to specific command identifiers."""
@staticmethod
- def from_dict(obj: Any) -> 'PermissionsLocationsAddToolApprovalDetailsCommands':
+ def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalCommands':
assert isinstance(obj, dict)
command_identifiers = from_list(from_str, obj.get("commandIdentifiers"))
- kind = PermissionDecisionApproveForLocationApprovalCommandsKind(obj.get("kind"))
- return PermissionsLocationsAddToolApprovalDetailsCommands(command_identifiers, kind)
+ return PermissionDecisionApproveForSessionApprovalCommands(command_identifiers)
def to_dict(self) -> dict:
result: dict = {}
result["commandIdentifiers"] = from_list(from_str, self.command_identifiers)
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalCommandsKind, self.kind)
+ result["kind"] = self.kind
return result
+# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
-class UserToolSessionApprovalCommands:
- """Schema for the `UserToolSessionApprovalCommands` type."""
+class PermissionsLocationsAddToolApprovalDetailsCommands:
+ """Schema for the `PermissionsLocationsAddToolApprovalDetailsCommands` type."""
command_identifiers: list[str]
- """Command identifiers approved by the user"""
+ """Command identifiers covered by this approval."""
- kind: PermissionDecisionApproveForLocationApprovalCommandsKind
- """Command approval kind"""
+ kind: ClassVar[str] = "commands"
+ """Approval scoped to specific command identifiers."""
@staticmethod
- def from_dict(obj: Any) -> 'UserToolSessionApprovalCommands':
+ def from_dict(obj: Any) -> 'PermissionsLocationsAddToolApprovalDetailsCommands':
assert isinstance(obj, dict)
command_identifiers = from_list(from_str, obj.get("commandIdentifiers"))
- kind = PermissionDecisionApproveForLocationApprovalCommandsKind(obj.get("kind"))
- return UserToolSessionApprovalCommands(command_identifiers, kind)
+ return PermissionsLocationsAddToolApprovalDetailsCommands(command_identifiers)
def to_dict(self) -> dict:
result: dict = {}
result["commandIdentifiers"] = from_list(from_str, self.command_identifiers)
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalCommandsKind, self.kind)
+ result["kind"] = self.kind
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -8075,7 +8209,7 @@ def to_dict(self) -> dict:
class PermissionDecisionApproveForLocationApprovalCustomTool:
"""Schema for the `PermissionDecisionApproveForLocationApprovalCustomTool` type."""
- kind: PermissionDecisionApproveForLocationApprovalCustomToolKind
+ kind: ClassVar[str] = "custom-tool"
"""Approval covering a custom tool."""
tool_name: str
@@ -8084,13 +8218,12 @@ class PermissionDecisionApproveForLocationApprovalCustomTool:
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalCustomTool':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalCustomToolKind(obj.get("kind"))
tool_name = from_str(obj.get("toolName"))
- return PermissionDecisionApproveForLocationApprovalCustomTool(kind, tool_name)
+ return PermissionDecisionApproveForLocationApprovalCustomTool(tool_name)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalCustomToolKind, self.kind)
+ result["kind"] = self.kind
result["toolName"] = from_str(self.tool_name)
return result
@@ -8099,7 +8232,7 @@ def to_dict(self) -> dict:
class PermissionDecisionApproveForSessionApprovalCustomTool:
"""Schema for the `PermissionDecisionApproveForSessionApprovalCustomTool` type."""
- kind: PermissionDecisionApproveForLocationApprovalCustomToolKind
+ kind: ClassVar[str] = "custom-tool"
"""Approval covering a custom tool."""
tool_name: str
@@ -8108,13 +8241,12 @@ class PermissionDecisionApproveForSessionApprovalCustomTool:
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalCustomTool':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalCustomToolKind(obj.get("kind"))
tool_name = from_str(obj.get("toolName"))
- return PermissionDecisionApproveForSessionApprovalCustomTool(kind, tool_name)
+ return PermissionDecisionApproveForSessionApprovalCustomTool(tool_name)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalCustomToolKind, self.kind)
+ result["kind"] = self.kind
result["toolName"] = from_str(self.tool_name)
return result
@@ -8123,7 +8255,7 @@ def to_dict(self) -> dict:
class PermissionsLocationsAddToolApprovalDetailsCustomTool:
"""Schema for the `PermissionsLocationsAddToolApprovalDetailsCustomTool` type."""
- kind: PermissionDecisionApproveForLocationApprovalCustomToolKind
+ kind: ClassVar[str] = "custom-tool"
"""Approval covering a custom tool."""
tool_name: str
@@ -8132,36 +8264,12 @@ class PermissionsLocationsAddToolApprovalDetailsCustomTool:
@staticmethod
def from_dict(obj: Any) -> 'PermissionsLocationsAddToolApprovalDetailsCustomTool':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalCustomToolKind(obj.get("kind"))
- tool_name = from_str(obj.get("toolName"))
- return PermissionsLocationsAddToolApprovalDetailsCustomTool(kind, tool_name)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalCustomToolKind, self.kind)
- result["toolName"] = from_str(self.tool_name)
- return result
-
-@dataclass
-class UserToolSessionApprovalCustomTool:
- """Schema for the `UserToolSessionApprovalCustomTool` type."""
-
- kind: PermissionDecisionApproveForLocationApprovalCustomToolKind
- """Custom tool approval kind"""
-
- tool_name: str
- """Custom tool name"""
-
- @staticmethod
- def from_dict(obj: Any) -> 'UserToolSessionApprovalCustomTool':
- assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalCustomToolKind(obj.get("kind"))
tool_name = from_str(obj.get("toolName"))
- return UserToolSessionApprovalCustomTool(kind, tool_name)
+ return PermissionsLocationsAddToolApprovalDetailsCustomTool(tool_name)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalCustomToolKind, self.kind)
+ result["kind"] = self.kind
result["toolName"] = from_str(self.tool_name)
return result
@@ -8170,7 +8278,7 @@ def to_dict(self) -> dict:
class PermissionDecisionApproveForLocationApprovalExtensionManagement:
"""Schema for the `PermissionDecisionApproveForLocationApprovalExtensionManagement` type."""
- kind: PermissionDecisionApproveForLocationApprovalExtensionManagementKind
+ kind: ClassVar[str] = "extension-management"
"""Approval covering extension lifecycle operations such as enable, disable, or reload."""
operation: str | None = None
@@ -8181,13 +8289,12 @@ class PermissionDecisionApproveForLocationApprovalExtensionManagement:
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalExtensionManagement':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalExtensionManagementKind(obj.get("kind"))
operation = from_union([from_str, from_none], obj.get("operation"))
- return PermissionDecisionApproveForLocationApprovalExtensionManagement(kind, operation)
+ return PermissionDecisionApproveForLocationApprovalExtensionManagement(operation)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalExtensionManagementKind, self.kind)
+ result["kind"] = self.kind
if self.operation is not None:
result["operation"] = from_union([from_str, from_none], self.operation)
return result
@@ -8197,7 +8304,7 @@ def to_dict(self) -> dict:
class PermissionDecisionApproveForSessionApprovalExtensionManagement:
"""Schema for the `PermissionDecisionApproveForSessionApprovalExtensionManagement` type."""
- kind: PermissionDecisionApproveForLocationApprovalExtensionManagementKind
+ kind: ClassVar[str] = "extension-management"
"""Approval covering extension lifecycle operations such as enable, disable, or reload."""
operation: str | None = None
@@ -8208,13 +8315,12 @@ class PermissionDecisionApproveForSessionApprovalExtensionManagement:
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalExtensionManagement':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalExtensionManagementKind(obj.get("kind"))
operation = from_union([from_str, from_none], obj.get("operation"))
- return PermissionDecisionApproveForSessionApprovalExtensionManagement(kind, operation)
+ return PermissionDecisionApproveForSessionApprovalExtensionManagement(operation)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalExtensionManagementKind, self.kind)
+ result["kind"] = self.kind
if self.operation is not None:
result["operation"] = from_union([from_str, from_none], self.operation)
return result
@@ -8224,7 +8330,7 @@ def to_dict(self) -> dict:
class PermissionsLocationsAddToolApprovalDetailsExtensionManagement:
"""Schema for the `PermissionsLocationsAddToolApprovalDetailsExtensionManagement` type."""
- kind: PermissionDecisionApproveForLocationApprovalExtensionManagementKind
+ kind: ClassVar[str] = "extension-management"
"""Approval covering extension lifecycle operations such as enable, disable, or reload."""
operation: str | None = None
@@ -8235,13 +8341,12 @@ class PermissionsLocationsAddToolApprovalDetailsExtensionManagement:
@staticmethod
def from_dict(obj: Any) -> 'PermissionsLocationsAddToolApprovalDetailsExtensionManagement':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalExtensionManagementKind(obj.get("kind"))
operation = from_union([from_str, from_none], obj.get("operation"))
- return PermissionsLocationsAddToolApprovalDetailsExtensionManagement(kind, operation)
+ return PermissionsLocationsAddToolApprovalDetailsExtensionManagement(operation)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalExtensionManagementKind, self.kind)
+ result["kind"] = self.kind
if self.operation is not None:
result["operation"] = from_union([from_str, from_none], self.operation)
return result
@@ -8251,7 +8356,7 @@ def to_dict(self) -> dict:
class PermissionDecisionApproveForLocationApprovalMCP:
"""Schema for the `PermissionDecisionApproveForLocationApprovalMcp` type."""
- kind: PermissionDecisionApproveForLocationApprovalMCPKind
+ kind: ClassVar[str] = "mcp"
"""Approval covering an MCP tool."""
server_name: str
@@ -8263,14 +8368,13 @@ class PermissionDecisionApproveForLocationApprovalMCP:
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalMCP':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalMCPKind(obj.get("kind"))
server_name = from_str(obj.get("serverName"))
tool_name = from_union([from_none, from_str], obj.get("toolName"))
- return PermissionDecisionApproveForLocationApprovalMCP(kind, server_name, tool_name)
+ return PermissionDecisionApproveForLocationApprovalMCP(server_name, tool_name)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalMCPKind, self.kind)
+ result["kind"] = self.kind
result["serverName"] = from_str(self.server_name)
result["toolName"] = from_union([from_none, from_str], self.tool_name)
return result
@@ -8280,7 +8384,7 @@ def to_dict(self) -> dict:
class PermissionDecisionApproveForSessionApprovalMCP:
"""Schema for the `PermissionDecisionApproveForSessionApprovalMcp` type."""
- kind: PermissionDecisionApproveForLocationApprovalMCPKind
+ kind: ClassVar[str] = "mcp"
"""Approval covering an MCP tool."""
server_name: str
@@ -8292,14 +8396,13 @@ class PermissionDecisionApproveForSessionApprovalMCP:
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalMCP':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalMCPKind(obj.get("kind"))
server_name = from_str(obj.get("serverName"))
tool_name = from_union([from_none, from_str], obj.get("toolName"))
- return PermissionDecisionApproveForSessionApprovalMCP(kind, server_name, tool_name)
+ return PermissionDecisionApproveForSessionApprovalMCP(server_name, tool_name)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalMCPKind, self.kind)
+ result["kind"] = self.kind
result["serverName"] = from_str(self.server_name)
result["toolName"] = from_union([from_none, from_str], self.tool_name)
return result
@@ -8309,7 +8412,7 @@ def to_dict(self) -> dict:
class PermissionsLocationsAddToolApprovalDetailsMCP:
"""Schema for the `PermissionsLocationsAddToolApprovalDetailsMcp` type."""
- kind: PermissionDecisionApproveForLocationApprovalMCPKind
+ kind: ClassVar[str] = "mcp"
"""Approval covering an MCP tool."""
server_name: str
@@ -8321,42 +8424,13 @@ class PermissionsLocationsAddToolApprovalDetailsMCP:
@staticmethod
def from_dict(obj: Any) -> 'PermissionsLocationsAddToolApprovalDetailsMCP':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalMCPKind(obj.get("kind"))
- server_name = from_str(obj.get("serverName"))
- tool_name = from_union([from_none, from_str], obj.get("toolName"))
- return PermissionsLocationsAddToolApprovalDetailsMCP(kind, server_name, tool_name)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalMCPKind, self.kind)
- result["serverName"] = from_str(self.server_name)
- result["toolName"] = from_union([from_none, from_str], self.tool_name)
- return result
-
-@dataclass
-class UserToolSessionApprovalMCP:
- """Schema for the `UserToolSessionApprovalMcp` type."""
-
- kind: PermissionDecisionApproveForLocationApprovalMCPKind
- """MCP tool approval kind"""
-
- server_name: str
- """MCP server name"""
-
- tool_name: str | None = None
- """Optional MCP tool name, or null for all tools on the server"""
-
- @staticmethod
- def from_dict(obj: Any) -> 'UserToolSessionApprovalMCP':
- assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalMCPKind(obj.get("kind"))
server_name = from_str(obj.get("serverName"))
tool_name = from_union([from_none, from_str], obj.get("toolName"))
- return UserToolSessionApprovalMCP(kind, server_name, tool_name)
+ return PermissionsLocationsAddToolApprovalDetailsMCP(server_name, tool_name)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalMCPKind, self.kind)
+ result["kind"] = self.kind
result["serverName"] = from_str(self.server_name)
result["toolName"] = from_union([from_none, from_str], self.tool_name)
return result
@@ -8366,7 +8440,7 @@ def to_dict(self) -> dict:
class PermissionDecisionApproveForLocationApprovalMCPSampling:
"""Schema for the `PermissionDecisionApproveForLocationApprovalMcpSampling` type."""
- kind: PermissionDecisionApproveForLocationApprovalMCPSamplingKind
+ kind: ClassVar[str] = "mcp-sampling"
"""Approval covering MCP sampling requests for a server."""
server_name: str
@@ -8375,13 +8449,12 @@ class PermissionDecisionApproveForLocationApprovalMCPSampling:
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalMCPSampling':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalMCPSamplingKind(obj.get("kind"))
server_name = from_str(obj.get("serverName"))
- return PermissionDecisionApproveForLocationApprovalMCPSampling(kind, server_name)
+ return PermissionDecisionApproveForLocationApprovalMCPSampling(server_name)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalMCPSamplingKind, self.kind)
+ result["kind"] = self.kind
result["serverName"] = from_str(self.server_name)
return result
@@ -8390,7 +8463,7 @@ def to_dict(self) -> dict:
class PermissionDecisionApproveForSessionApprovalMCPSampling:
"""Schema for the `PermissionDecisionApproveForSessionApprovalMcpSampling` type."""
- kind: PermissionDecisionApproveForLocationApprovalMCPSamplingKind
+ kind: ClassVar[str] = "mcp-sampling"
"""Approval covering MCP sampling requests for a server."""
server_name: str
@@ -8399,13 +8472,12 @@ class PermissionDecisionApproveForSessionApprovalMCPSampling:
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalMCPSampling':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalMCPSamplingKind(obj.get("kind"))
server_name = from_str(obj.get("serverName"))
- return PermissionDecisionApproveForSessionApprovalMCPSampling(kind, server_name)
+ return PermissionDecisionApproveForSessionApprovalMCPSampling(server_name)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalMCPSamplingKind, self.kind)
+ result["kind"] = self.kind
result["serverName"] = from_str(self.server_name)
return result
@@ -8414,7 +8486,7 @@ def to_dict(self) -> dict:
class PermissionsLocationsAddToolApprovalDetailsMCPSampling:
"""Schema for the `PermissionsLocationsAddToolApprovalDetailsMcpSampling` type."""
- kind: PermissionDecisionApproveForLocationApprovalMCPSamplingKind
+ kind: ClassVar[str] = "mcp-sampling"
"""Approval covering MCP sampling requests for a server."""
server_name: str
@@ -8423,13 +8495,12 @@ class PermissionsLocationsAddToolApprovalDetailsMCPSampling:
@staticmethod
def from_dict(obj: Any) -> 'PermissionsLocationsAddToolApprovalDetailsMCPSampling':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalMCPSamplingKind(obj.get("kind"))
server_name = from_str(obj.get("serverName"))
- return PermissionsLocationsAddToolApprovalDetailsMCPSampling(kind, server_name)
+ return PermissionsLocationsAddToolApprovalDetailsMCPSampling(server_name)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalMCPSamplingKind, self.kind)
+ result["kind"] = self.kind
result["serverName"] = from_str(self.server_name)
return result
@@ -8438,18 +8509,17 @@ def to_dict(self) -> dict:
class PermissionDecisionApproveForLocationApprovalMemory:
"""Schema for the `PermissionDecisionApproveForLocationApprovalMemory` type."""
- kind: PermissionDecisionApproveForLocationApprovalMemoryKind
+ kind: ClassVar[str] = "memory"
"""Approval covering writes to long-term memory."""
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalMemory':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalMemoryKind(obj.get("kind"))
- return PermissionDecisionApproveForLocationApprovalMemory(kind)
+ return PermissionDecisionApproveForLocationApprovalMemory()
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalMemoryKind, self.kind)
+ result["kind"] = self.kind
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -8457,18 +8527,17 @@ def to_dict(self) -> dict:
class PermissionDecisionApproveForSessionApprovalMemory:
"""Schema for the `PermissionDecisionApproveForSessionApprovalMemory` type."""
- kind: PermissionDecisionApproveForLocationApprovalMemoryKind
+ kind: ClassVar[str] = "memory"
"""Approval covering writes to long-term memory."""
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalMemory':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalMemoryKind(obj.get("kind"))
- return PermissionDecisionApproveForSessionApprovalMemory(kind)
+ return PermissionDecisionApproveForSessionApprovalMemory()
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalMemoryKind, self.kind)
+ result["kind"] = self.kind
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -8476,36 +8545,17 @@ def to_dict(self) -> dict:
class PermissionsLocationsAddToolApprovalDetailsMemory:
"""Schema for the `PermissionsLocationsAddToolApprovalDetailsMemory` type."""
- kind: PermissionDecisionApproveForLocationApprovalMemoryKind
+ kind: ClassVar[str] = "memory"
"""Approval covering writes to long-term memory."""
@staticmethod
def from_dict(obj: Any) -> 'PermissionsLocationsAddToolApprovalDetailsMemory':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalMemoryKind(obj.get("kind"))
- return PermissionsLocationsAddToolApprovalDetailsMemory(kind)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalMemoryKind, self.kind)
- return result
-
-@dataclass
-class UserToolSessionApprovalMemory:
- """Schema for the `UserToolSessionApprovalMemory` type."""
-
- kind: PermissionDecisionApproveForLocationApprovalMemoryKind
- """Memory approval kind"""
-
- @staticmethod
- def from_dict(obj: Any) -> 'UserToolSessionApprovalMemory':
- assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalMemoryKind(obj.get("kind"))
- return UserToolSessionApprovalMemory(kind)
+ return PermissionsLocationsAddToolApprovalDetailsMemory()
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalMemoryKind, self.kind)
+ result["kind"] = self.kind
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -8513,18 +8563,17 @@ def to_dict(self) -> dict:
class PermissionDecisionApproveForLocationApprovalRead:
"""Schema for the `PermissionDecisionApproveForLocationApprovalRead` type."""
- kind: PermissionDecisionApproveForLocationApprovalReadKind
+ kind: ClassVar[str] = "read"
"""Approval covering read-only filesystem operations."""
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalRead':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalReadKind(obj.get("kind"))
- return PermissionDecisionApproveForLocationApprovalRead(kind)
+ return PermissionDecisionApproveForLocationApprovalRead()
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalReadKind, self.kind)
+ result["kind"] = self.kind
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -8532,18 +8581,17 @@ def to_dict(self) -> dict:
class PermissionDecisionApproveForSessionApprovalRead:
"""Schema for the `PermissionDecisionApproveForSessionApprovalRead` type."""
- kind: PermissionDecisionApproveForLocationApprovalReadKind
+ kind: ClassVar[str] = "read"
"""Approval covering read-only filesystem operations."""
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalRead':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalReadKind(obj.get("kind"))
- return PermissionDecisionApproveForSessionApprovalRead(kind)
+ return PermissionDecisionApproveForSessionApprovalRead()
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalReadKind, self.kind)
+ result["kind"] = self.kind
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -8551,36 +8599,17 @@ def to_dict(self) -> dict:
class PermissionsLocationsAddToolApprovalDetailsRead:
"""Schema for the `PermissionsLocationsAddToolApprovalDetailsRead` type."""
- kind: PermissionDecisionApproveForLocationApprovalReadKind
+ kind: ClassVar[str] = "read"
"""Approval covering read-only filesystem operations."""
@staticmethod
def from_dict(obj: Any) -> 'PermissionsLocationsAddToolApprovalDetailsRead':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalReadKind(obj.get("kind"))
- return PermissionsLocationsAddToolApprovalDetailsRead(kind)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalReadKind, self.kind)
- return result
-
-@dataclass
-class UserToolSessionApprovalRead:
- """Schema for the `UserToolSessionApprovalRead` type."""
-
- kind: PermissionDecisionApproveForLocationApprovalReadKind
- """Read approval kind"""
-
- @staticmethod
- def from_dict(obj: Any) -> 'UserToolSessionApprovalRead':
- assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalReadKind(obj.get("kind"))
- return UserToolSessionApprovalRead(kind)
+ return PermissionsLocationsAddToolApprovalDetailsRead()
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalReadKind, self.kind)
+ result["kind"] = self.kind
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -8588,18 +8617,17 @@ def to_dict(self) -> dict:
class PermissionDecisionApproveForLocationApprovalWrite:
"""Schema for the `PermissionDecisionApproveForLocationApprovalWrite` type."""
- kind: PermissionDecisionApproveForLocationApprovalWriteKind
+ kind: ClassVar[str] = "write"
"""Approval covering filesystem write operations."""
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalWrite':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalWriteKind(obj.get("kind"))
- return PermissionDecisionApproveForLocationApprovalWrite(kind)
+ return PermissionDecisionApproveForLocationApprovalWrite()
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalWriteKind, self.kind)
+ result["kind"] = self.kind
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -8607,18 +8635,17 @@ def to_dict(self) -> dict:
class PermissionDecisionApproveForSessionApprovalWrite:
"""Schema for the `PermissionDecisionApproveForSessionApprovalWrite` type."""
- kind: PermissionDecisionApproveForLocationApprovalWriteKind
+ kind: ClassVar[str] = "write"
"""Approval covering filesystem write operations."""
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalWrite':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalWriteKind(obj.get("kind"))
- return PermissionDecisionApproveForSessionApprovalWrite(kind)
+ return PermissionDecisionApproveForSessionApprovalWrite()
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalWriteKind, self.kind)
+ result["kind"] = self.kind
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -8626,36 +8653,47 @@ def to_dict(self) -> dict:
class PermissionsLocationsAddToolApprovalDetailsWrite:
"""Schema for the `PermissionsLocationsAddToolApprovalDetailsWrite` type."""
- kind: PermissionDecisionApproveForLocationApprovalWriteKind
+ kind: ClassVar[str] = "write"
"""Approval covering filesystem write operations."""
@staticmethod
def from_dict(obj: Any) -> 'PermissionsLocationsAddToolApprovalDetailsWrite':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalWriteKind(obj.get("kind"))
- return PermissionsLocationsAddToolApprovalDetailsWrite(kind)
+ return PermissionsLocationsAddToolApprovalDetailsWrite()
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalWriteKind, self.kind)
+ result["kind"] = self.kind
return result
+# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
-class UserToolSessionApprovalWrite:
- """Schema for the `UserToolSessionApprovalWrite` type."""
+class PermissionDecisionApproveForSession:
+ """Schema for the `PermissionDecisionApproveForSession` type."""
+
+ kind: ClassVar[str] = "approve-for-session"
+ """Approve and remember for the rest of the session"""
+
+ approval: PermissionDecisionApproveForSessionApproval | None = None
+ """Session-scoped approval to remember (tool prompts only; omitted for path/url prompts)"""
- kind: PermissionDecisionApproveForLocationApprovalWriteKind
- """Write approval kind"""
+ domain: str | None = None
+ """URL domain to approve for the rest of the session (URL prompts only)"""
@staticmethod
- def from_dict(obj: Any) -> 'UserToolSessionApprovalWrite':
+ def from_dict(obj: Any) -> 'PermissionDecisionApproveForSession':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalWriteKind(obj.get("kind"))
- return UserToolSessionApprovalWrite(kind)
+ approval = from_union([_load_PermissionDecisionApproveForSessionApproval, from_none], obj.get("approval"))
+ domain = from_union([from_str, from_none], obj.get("domain"))
+ return PermissionDecisionApproveForSession(approval, domain)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalWriteKind, self.kind)
+ result["kind"] = self.kind
+ if self.approval is not None:
+ result["approval"] = from_union([lambda x: (x).to_dict(), from_none], self.approval)
+ if self.domain is not None:
+ result["domain"] = from_union([from_str, from_none], self.domain)
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -8663,18 +8701,17 @@ def to_dict(self) -> dict:
class PermissionDecisionApproveOnce:
"""Schema for the `PermissionDecisionApproveOnce` type."""
- kind: PermissionDecisionApproveOnceKind
+ kind: ClassVar[str] = "approve-once"
"""Approve this single request only"""
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionApproveOnce':
assert isinstance(obj, dict)
- kind = PermissionDecisionApproveOnceKind(obj.get("kind"))
- return PermissionDecisionApproveOnce(kind)
+ return PermissionDecisionApproveOnce()
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveOnceKind, self.kind)
+ result["kind"] = self.kind
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -8685,20 +8722,19 @@ class PermissionDecisionApprovePermanently:
domain: str
"""URL domain to approve permanently"""
- kind: PermissionDecisionApprovePermanentlyKind
+ kind: ClassVar[str] = "approve-permanently"
"""Approve and persist across sessions (URL prompts only)"""
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionApprovePermanently':
assert isinstance(obj, dict)
domain = from_str(obj.get("domain"))
- kind = PermissionDecisionApprovePermanentlyKind(obj.get("kind"))
- return PermissionDecisionApprovePermanently(domain, kind)
+ return PermissionDecisionApprovePermanently(domain)
def to_dict(self) -> dict:
result: dict = {}
result["domain"] = from_str(self.domain)
- result["kind"] = to_enum(PermissionDecisionApprovePermanentlyKind, self.kind)
+ result["kind"] = self.kind
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -8706,18 +8742,17 @@ def to_dict(self) -> dict:
class PermissionDecisionApproved:
"""Schema for the `PermissionDecisionApproved` type."""
- kind: PermissionDecisionApprovedKind
+ kind: ClassVar[str] = "approved"
"""The permission request was approved"""
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionApproved':
assert isinstance(obj, dict)
- kind = PermissionDecisionApprovedKind(obj.get("kind"))
- return PermissionDecisionApproved(kind)
+ return PermissionDecisionApproved()
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApprovedKind, self.kind)
+ result["kind"] = self.kind
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -8728,7 +8763,7 @@ class PermissionDecisionApprovedForLocation:
approval: UserToolSessionApproval
"""The approval to persist for this location"""
- kind: PermissionDecisionApprovedForLocationKind
+ kind: ClassVar[str] = "approved-for-location"
"""Approved and persisted for this project location"""
location_key: str
@@ -8738,14 +8773,13 @@ class PermissionDecisionApprovedForLocation:
def from_dict(obj: Any) -> 'PermissionDecisionApprovedForLocation':
assert isinstance(obj, dict)
approval = UserToolSessionApproval.from_dict(obj.get("approval"))
- kind = PermissionDecisionApprovedForLocationKind(obj.get("kind"))
location_key = from_str(obj.get("locationKey"))
- return PermissionDecisionApprovedForLocation(approval, kind, location_key)
+ return PermissionDecisionApprovedForLocation(approval, location_key)
def to_dict(self) -> dict:
result: dict = {}
result["approval"] = to_class(UserToolSessionApproval, self.approval)
- result["kind"] = to_enum(PermissionDecisionApprovedForLocationKind, self.kind)
+ result["kind"] = self.kind
result["locationKey"] = from_str(self.location_key)
return result
@@ -8757,20 +8791,19 @@ class PermissionDecisionApprovedForSession:
approval: UserToolSessionApproval
"""The approval to add as a session-scoped rule"""
- kind: PermissionDecisionApprovedForSessionKind
+ kind: ClassVar[str] = "approved-for-session"
"""Approved and remembered for the rest of the session"""
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionApprovedForSession':
assert isinstance(obj, dict)
approval = UserToolSessionApproval.from_dict(obj.get("approval"))
- kind = PermissionDecisionApprovedForSessionKind(obj.get("kind"))
- return PermissionDecisionApprovedForSession(approval, kind)
+ return PermissionDecisionApprovedForSession(approval)
def to_dict(self) -> dict:
result: dict = {}
result["approval"] = to_class(UserToolSessionApproval, self.approval)
- result["kind"] = to_enum(PermissionDecisionApprovedForSessionKind, self.kind)
+ result["kind"] = self.kind
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -8778,7 +8811,7 @@ def to_dict(self) -> dict:
class PermissionDecisionCancelled:
"""Schema for the `PermissionDecisionCancelled` type."""
- kind: PermissionDecisionCancelledKind
+ kind: ClassVar[str] = "cancelled"
"""The permission request was cancelled before a response was used"""
reason: str | None = None
@@ -8787,13 +8820,12 @@ class PermissionDecisionCancelled:
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionCancelled':
assert isinstance(obj, dict)
- kind = PermissionDecisionCancelledKind(obj.get("kind"))
reason = from_union([from_str, from_none], obj.get("reason"))
- return PermissionDecisionCancelled(kind, reason)
+ return PermissionDecisionCancelled(reason)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionCancelledKind, self.kind)
+ result["kind"] = self.kind
if self.reason is not None:
result["reason"] = from_union([from_str, from_none], self.reason)
return result
@@ -8803,7 +8835,7 @@ def to_dict(self) -> dict:
class PermissionDecisionDeniedByContentExclusionPolicy:
"""Schema for the `PermissionDecisionDeniedByContentExclusionPolicy` type."""
- kind: PermissionDecisionDeniedByContentExclusionPolicyKind
+ kind: ClassVar[str] = "denied-by-content-exclusion-policy"
"""Denied by the organization's content exclusion policy"""
message: str
@@ -8815,14 +8847,13 @@ class PermissionDecisionDeniedByContentExclusionPolicy:
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionDeniedByContentExclusionPolicy':
assert isinstance(obj, dict)
- kind = PermissionDecisionDeniedByContentExclusionPolicyKind(obj.get("kind"))
message = from_str(obj.get("message"))
path = from_str(obj.get("path"))
- return PermissionDecisionDeniedByContentExclusionPolicy(kind, message, path)
+ return PermissionDecisionDeniedByContentExclusionPolicy(message, path)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionDeniedByContentExclusionPolicyKind, self.kind)
+ result["kind"] = self.kind
result["message"] = from_str(self.message)
result["path"] = from_str(self.path)
return result
@@ -8832,7 +8863,7 @@ def to_dict(self) -> dict:
class PermissionDecisionDeniedByPermissionRequestHook:
"""Schema for the `PermissionDecisionDeniedByPermissionRequestHook` type."""
- kind: PermissionDecisionDeniedByPermissionRequestHookKind
+ kind: ClassVar[str] = "denied-by-permission-request-hook"
"""Denied by a permission request hook registered by an extension or plugin"""
interrupt: bool | None = None
@@ -8844,14 +8875,13 @@ class PermissionDecisionDeniedByPermissionRequestHook:
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionDeniedByPermissionRequestHook':
assert isinstance(obj, dict)
- kind = PermissionDecisionDeniedByPermissionRequestHookKind(obj.get("kind"))
interrupt = from_union([from_bool, from_none], obj.get("interrupt"))
message = from_union([from_str, from_none], obj.get("message"))
- return PermissionDecisionDeniedByPermissionRequestHook(kind, interrupt, message)
+ return PermissionDecisionDeniedByPermissionRequestHook(interrupt, message)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionDeniedByPermissionRequestHookKind, self.kind)
+ result["kind"] = self.kind
if self.interrupt is not None:
result["interrupt"] = from_union([from_bool, from_none], self.interrupt)
if self.message is not None:
@@ -8863,7 +8893,7 @@ def to_dict(self) -> dict:
class PermissionDecisionDeniedByRules:
"""Schema for the `PermissionDecisionDeniedByRules` type."""
- kind: PermissionDecisionDeniedByRulesKind
+ kind: ClassVar[str] = "denied-by-rules"
"""Denied because approval rules explicitly blocked it"""
rules: list[PermissionRule]
@@ -8872,13 +8902,12 @@ class PermissionDecisionDeniedByRules:
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionDeniedByRules':
assert isinstance(obj, dict)
- kind = PermissionDecisionDeniedByRulesKind(obj.get("kind"))
rules = from_list(PermissionRule.from_dict, obj.get("rules"))
- return PermissionDecisionDeniedByRules(kind, rules)
+ return PermissionDecisionDeniedByRules(rules)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionDeniedByRulesKind, self.kind)
+ result["kind"] = self.kind
result["rules"] = from_list(lambda x: to_class(PermissionRule, x), self.rules)
return result
@@ -8887,7 +8916,7 @@ def to_dict(self) -> dict:
class PermissionDecisionDeniedInteractivelyByUser:
"""Schema for the `PermissionDecisionDeniedInteractivelyByUser` type."""
- kind: PermissionDecisionDeniedInteractivelyByUserKind
+ kind: ClassVar[str] = "denied-interactively-by-user"
"""Denied by the user during an interactive prompt"""
feedback: str | None = None
@@ -8899,14 +8928,13 @@ class PermissionDecisionDeniedInteractivelyByUser:
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionDeniedInteractivelyByUser':
assert isinstance(obj, dict)
- kind = PermissionDecisionDeniedInteractivelyByUserKind(obj.get("kind"))
feedback = from_union([from_str, from_none], obj.get("feedback"))
force_reject = from_union([from_bool, from_none], obj.get("forceReject"))
- return PermissionDecisionDeniedInteractivelyByUser(kind, feedback, force_reject)
+ return PermissionDecisionDeniedInteractivelyByUser(feedback, force_reject)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionDeniedInteractivelyByUserKind, self.kind)
+ result["kind"] = self.kind
if self.feedback is not None:
result["feedback"] = from_union([from_str, from_none], self.feedback)
if self.force_reject is not None:
@@ -8918,18 +8946,17 @@ def to_dict(self) -> dict:
class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser:
"""Schema for the `PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser` type."""
- kind: PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind
+ kind: ClassVar[str] = "denied-no-approval-rule-and-could-not-request-from-user"
"""Denied because no approval rule matched and user confirmation was unavailable"""
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser':
assert isinstance(obj, dict)
- kind = PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind(obj.get("kind"))
- return PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser(kind)
+ return PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser()
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind, self.kind)
+ result["kind"] = self.kind
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -8937,7 +8964,7 @@ def to_dict(self) -> dict:
class PermissionDecisionReject:
"""Schema for the `PermissionDecisionReject` type."""
- kind: PermissionDecisionRejectKind
+ kind: ClassVar[str] = "reject"
"""Reject the request"""
feedback: str | None = None
@@ -8946,13 +8973,12 @@ class PermissionDecisionReject:
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionReject':
assert isinstance(obj, dict)
- kind = PermissionDecisionRejectKind(obj.get("kind"))
feedback = from_union([from_str, from_none], obj.get("feedback"))
- return PermissionDecisionReject(kind, feedback)
+ return PermissionDecisionReject(feedback)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionRejectKind, self.kind)
+ result["kind"] = self.kind
if self.feedback is not None:
result["feedback"] = from_union([from_str, from_none], self.feedback)
return result
@@ -8962,18 +8988,17 @@ def to_dict(self) -> dict:
class PermissionDecisionUserNotAvailable:
"""Schema for the `PermissionDecisionUserNotAvailable` type."""
- kind: PermissionDecisionUserNotAvailableKind
+ kind: ClassVar[str] = "user-not-available"
"""No user is available to confirm the request"""
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionUserNotAvailable':
assert isinstance(obj, dict)
- kind = PermissionDecisionUserNotAvailableKind(obj.get("kind"))
- return PermissionDecisionUserNotAvailable(kind)
+ return PermissionDecisionUserNotAvailable()
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionDecisionUserNotAvailableKind, self.kind)
+ result["kind"] = self.kind
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -9159,40 +9184,6 @@ def to_dict(self) -> dict:
result["kind"] = to_enum(QueuePendingItemsKind, self.kind)
return result
-# Experimental: this type is part of an experimental API and may change or be removed.
-@dataclass
-class QueuedCommandResult:
- """Result of the queued command execution.
-
- Schema for the `QueuedCommandHandled` type.
-
- Schema for the `QueuedCommandNotHandled` type.
- """
- handled: bool
- """The host actually executed the queued command.
-
- The host did not execute the queued command. Unblocks the queue without claiming the
- command was processed (e.g. when the handler threw before completing).
- """
- stop_processing_queue: bool | None = None
- """When true, the runtime will not process subsequent queued commands until a new request
- comes in.
- """
-
- @staticmethod
- def from_dict(obj: Any) -> 'QueuedCommandResult':
- assert isinstance(obj, dict)
- handled = from_bool(obj.get("handled"))
- stop_processing_queue = from_union([from_bool, from_none], obj.get("stopProcessingQueue"))
- return QueuedCommandResult(handled, stop_processing_queue)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["handled"] = from_bool(self.handled)
- if self.stop_processing_queue is not None:
- result["stopProcessingQueue"] = from_union([from_bool, from_none], self.stop_processing_queue)
- return result
-
# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
class RemoteEnableRequest:
@@ -9290,7 +9281,7 @@ class SendAttachmentBlob:
mime_type: str
"""MIME type of the inline data"""
- type: SendAttachmentBlobType
+ type: ClassVar[str] = "blob"
"""Attachment type discriminator"""
display_name: str | None = None
@@ -9301,15 +9292,14 @@ def from_dict(obj: Any) -> 'SendAttachmentBlob':
assert isinstance(obj, dict)
data = from_str(obj.get("data"))
mime_type = from_str(obj.get("mimeType"))
- type = SendAttachmentBlobType(obj.get("type"))
display_name = from_union([from_str, from_none], obj.get("displayName"))
- return SendAttachmentBlob(data, mime_type, type, display_name)
+ return SendAttachmentBlob(data, mime_type, display_name)
def to_dict(self) -> dict:
result: dict = {}
result["data"] = from_str(self.data)
result["mimeType"] = from_str(self.mime_type)
- result["type"] = to_enum(SendAttachmentBlobType, self.type)
+ result["type"] = self.type
if self.display_name is not None:
result["displayName"] = from_union([from_str, from_none], self.display_name)
return result
@@ -9325,7 +9315,7 @@ class SendAttachmentFile:
path: str
"""Absolute file path"""
- type: SendAttachmentFileType
+ type: ClassVar[str] = "file"
"""Attachment type discriminator"""
line_range: SendAttachmentFileLineRange | None = None
@@ -9336,15 +9326,14 @@ def from_dict(obj: Any) -> 'SendAttachmentFile':
assert isinstance(obj, dict)
display_name = from_str(obj.get("displayName"))
path = from_str(obj.get("path"))
- type = SendAttachmentFileType(obj.get("type"))
line_range = from_union([SendAttachmentFileLineRange.from_dict, from_none], obj.get("lineRange"))
- return SendAttachmentFile(display_name, path, type, line_range)
+ return SendAttachmentFile(display_name, path, line_range)
def to_dict(self) -> dict:
result: dict = {}
result["displayName"] = from_str(self.display_name)
result["path"] = from_str(self.path)
- result["type"] = to_enum(SendAttachmentFileType, self.type)
+ result["type"] = self.type
if self.line_range is not None:
result["lineRange"] = from_union([lambda x: to_class(SendAttachmentFileLineRange, x), from_none], self.line_range)
return result
@@ -9366,7 +9355,7 @@ class SendAttachmentGithubReference:
title: str
"""Title of the referenced item"""
- type: SendAttachmentGithubReferenceType
+ type: ClassVar[str] = "github_reference"
"""Attachment type discriminator"""
url: str
@@ -9379,9 +9368,8 @@ def from_dict(obj: Any) -> 'SendAttachmentGithubReference':
reference_type = SendAttachmentGithubReferenceTypeEnum(obj.get("referenceType"))
state = from_str(obj.get("state"))
title = from_str(obj.get("title"))
- type = SendAttachmentGithubReferenceType(obj.get("type"))
url = from_str(obj.get("url"))
- return SendAttachmentGithubReference(number, reference_type, state, title, type, url)
+ return SendAttachmentGithubReference(number, reference_type, state, title, url)
def to_dict(self) -> dict:
result: dict = {}
@@ -9389,10 +9377,112 @@ def to_dict(self) -> dict:
result["referenceType"] = to_enum(SendAttachmentGithubReferenceTypeEnum, self.reference_type)
result["state"] = from_str(self.state)
result["title"] = from_str(self.title)
- result["type"] = to_enum(SendAttachmentGithubReferenceType, self.type)
+ result["type"] = self.type
result["url"] = from_str(self.url)
return result
+# Experimental: this type is part of an experimental API and may change or be removed.
+@dataclass
+class SendRequest:
+ """Parameters for sending a user message to the session"""
+
+ prompt: str
+ """The user message text"""
+
+ agent_mode: SendAgentMode | None = None
+ """The UI mode the agent was in when this message was sent. Defaults to the session's
+ current mode.
+ """
+ attachments: list[SendAttachment] | None = None
+ """Optional attachments (files, directories, selections, blobs, GitHub references) to
+ include with the message
+ """
+ billable: bool | None = None
+ """If false, this message will not trigger a Premium Request Unit charge. User messages
+ default to billable.
+ """
+ display_prompt: str | None = None
+ """If provided, this is shown in the timeline instead of `prompt`"""
+
+ mode: SendMode | None = None
+ """How to deliver the message. `enqueue` (default) appends to the message queue. `immediate`
+ interjects during an in-progress turn.
+ """
+ prepend: bool | None = None
+ """If true, adds the message to the front of the queue instead of the end"""
+
+ request_headers: dict[str, str] | None = None
+ """Custom HTTP headers to include in outbound model requests for this turn. Merged with
+ session-level provider headers; per-turn headers augment and overwrite session-level
+ headers with the same key.
+ """
+ required_tool: str | None = None
+ """If set, the request will fail if the named tool is not available when this message is
+ among the user messages at the start of the current exchange
+ """
+ source: Any = None
+ """Optional provenance tag copied to the resulting user.message event. Supported values are
+ `system`, `command-*`, and `schedule-*`.
+ """
+ traceparent: str | None = None
+ """W3C Trace Context traceparent header for distributed tracing of this agent turn"""
+
+ tracestate: str | None = None
+ """W3C Trace Context tracestate header for distributed tracing"""
+
+ wait: bool | None = None
+ """If true, await completion of the agentic loop for this message before returning. Defaults
+ to false (fire-and-forget). When true, the result still contains the same `messageId`;
+ the caller can rely on the agent having processed the message before the call resolves.
+ """
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'SendRequest':
+ assert isinstance(obj, dict)
+ prompt = from_str(obj.get("prompt"))
+ agent_mode = from_union([SendAgentMode, from_none], obj.get("agentMode"))
+ attachments = from_union([lambda x: from_list(_load_SendAttachment, x), from_none], obj.get("attachments"))
+ billable = from_union([from_bool, from_none], obj.get("billable"))
+ display_prompt = from_union([from_str, from_none], obj.get("displayPrompt"))
+ mode = from_union([SendMode, from_none], obj.get("mode"))
+ prepend = from_union([from_bool, from_none], obj.get("prepend"))
+ request_headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("requestHeaders"))
+ required_tool = from_union([from_str, from_none], obj.get("requiredTool"))
+ source = obj.get("source")
+ traceparent = from_union([from_str, from_none], obj.get("traceparent"))
+ tracestate = from_union([from_str, from_none], obj.get("tracestate"))
+ wait = from_union([from_bool, from_none], obj.get("wait"))
+ return SendRequest(prompt, agent_mode, attachments, billable, display_prompt, mode, prepend, request_headers, required_tool, source, traceparent, tracestate, wait)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["prompt"] = from_str(self.prompt)
+ if self.agent_mode is not None:
+ result["agentMode"] = from_union([lambda x: to_enum(SendAgentMode, x), from_none], self.agent_mode)
+ if self.attachments is not None:
+ result["attachments"] = from_union([lambda x: from_list(lambda x: (x).to_dict(), x), from_none], self.attachments)
+ if self.billable is not None:
+ result["billable"] = from_union([from_bool, from_none], self.billable)
+ if self.display_prompt is not None:
+ result["displayPrompt"] = from_union([from_str, from_none], self.display_prompt)
+ if self.mode is not None:
+ result["mode"] = from_union([lambda x: to_enum(SendMode, x), from_none], self.mode)
+ if self.prepend is not None:
+ result["prepend"] = from_union([from_bool, from_none], self.prepend)
+ if self.request_headers is not None:
+ result["requestHeaders"] = from_union([lambda x: from_dict(from_str, x), from_none], self.request_headers)
+ if self.required_tool is not None:
+ result["requiredTool"] = from_union([from_str, from_none], self.required_tool)
+ if self.source is not None:
+ result["source"] = self.source
+ if self.traceparent is not None:
+ result["traceparent"] = from_union([from_str, from_none], self.traceparent)
+ if self.tracestate is not None:
+ result["tracestate"] = from_union([from_str, from_none], self.tracestate)
+ if self.wait is not None:
+ result["wait"] = from_union([from_bool, from_none], self.wait)
+ return result
+
@dataclass
class ServerSkillList:
"""Skills discovered across global and project sources."""
@@ -9731,7 +9821,7 @@ class SlashCommandAgentPromptResult:
display_prompt: str
"""Prompt text to display to the user"""
- kind: SlashCommandAgentPromptResultKind
+ kind: ClassVar[str] = "agent-prompt"
"""Agent prompt result discriminator"""
prompt: str
@@ -9749,16 +9839,15 @@ class SlashCommandAgentPromptResult:
def from_dict(obj: Any) -> 'SlashCommandAgentPromptResult':
assert isinstance(obj, dict)
display_prompt = from_str(obj.get("displayPrompt"))
- kind = SlashCommandAgentPromptResultKind(obj.get("kind"))
prompt = from_str(obj.get("prompt"))
mode = from_union([SessionMode, from_none], obj.get("mode"))
runtime_settings_changed = from_union([from_bool, from_none], obj.get("runtimeSettingsChanged"))
- return SlashCommandAgentPromptResult(display_prompt, kind, prompt, mode, runtime_settings_changed)
+ return SlashCommandAgentPromptResult(display_prompt, prompt, mode, runtime_settings_changed)
def to_dict(self) -> dict:
result: dict = {}
result["displayPrompt"] = from_str(self.display_prompt)
- result["kind"] = to_enum(SlashCommandAgentPromptResultKind, self.kind)
+ result["kind"] = self.kind
result["prompt"] = from_str(self.prompt)
if self.mode is not None:
result["mode"] = from_union([lambda x: to_enum(SessionMode, x), from_none], self.mode)
@@ -9771,7 +9860,7 @@ def to_dict(self) -> dict:
class SlashCommandCompletedResult:
"""Schema for the `SlashCommandCompletedResult` type."""
- kind: SlashCommandCompletedResultKind
+ kind: ClassVar[str] = "completed"
"""Completed result discriminator"""
message: str | None = None
@@ -9785,14 +9874,13 @@ class SlashCommandCompletedResult:
@staticmethod
def from_dict(obj: Any) -> 'SlashCommandCompletedResult':
assert isinstance(obj, dict)
- kind = SlashCommandCompletedResultKind(obj.get("kind"))
message = from_union([from_str, from_none], obj.get("message"))
runtime_settings_changed = from_union([from_bool, from_none], obj.get("runtimeSettingsChanged"))
- return SlashCommandCompletedResult(kind, message, runtime_settings_changed)
+ return SlashCommandCompletedResult(message, runtime_settings_changed)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(SlashCommandCompletedResultKind, self.kind)
+ result["kind"] = self.kind
if self.message is not None:
result["message"] = from_union([from_str, from_none], self.message)
if self.runtime_settings_changed is not None:
@@ -9807,7 +9895,7 @@ class SlashCommandSelectSubcommandResult:
command: str
"""Parent command name that requires subcommand selection"""
- kind: SlashCommandSelectSubcommandResultKind
+ kind: ClassVar[str] = "select-subcommand"
"""Select subcommand result discriminator"""
options: list[SlashCommandSelectSubcommandOption]
@@ -9825,16 +9913,15 @@ class SlashCommandSelectSubcommandResult:
def from_dict(obj: Any) -> 'SlashCommandSelectSubcommandResult':
assert isinstance(obj, dict)
command = from_str(obj.get("command"))
- kind = SlashCommandSelectSubcommandResultKind(obj.get("kind"))
options = from_list(SlashCommandSelectSubcommandOption.from_dict, obj.get("options"))
title = from_str(obj.get("title"))
runtime_settings_changed = from_union([from_bool, from_none], obj.get("runtimeSettingsChanged"))
- return SlashCommandSelectSubcommandResult(command, kind, options, title, runtime_settings_changed)
+ return SlashCommandSelectSubcommandResult(command, options, title, runtime_settings_changed)
def to_dict(self) -> dict:
result: dict = {}
result["command"] = from_str(self.command)
- result["kind"] = to_enum(SlashCommandSelectSubcommandResultKind, self.kind)
+ result["kind"] = self.kind
result["options"] = from_list(lambda x: to_class(SlashCommandSelectSubcommandOption, x), self.options)
result["title"] = from_str(self.title)
if self.runtime_settings_changed is not None:
@@ -9895,7 +9982,7 @@ class TaskShellInfo:
status: TaskStatus
"""Current lifecycle status of the task"""
- type: TaskShellInfoType
+ type: ClassVar[str] = "shell"
"""Task kind"""
can_promote_to_background: bool | None = None
@@ -9922,13 +10009,12 @@ def from_dict(obj: Any) -> 'TaskShellInfo':
id = from_str(obj.get("id"))
started_at = from_datetime(obj.get("startedAt"))
status = TaskStatus(obj.get("status"))
- type = TaskShellInfoType(obj.get("type"))
can_promote_to_background = from_union([from_bool, from_none], obj.get("canPromoteToBackground"))
completed_at = from_union([from_datetime, from_none], obj.get("completedAt"))
execution_mode = from_union([TaskExecutionMode, from_none], obj.get("executionMode"))
log_path = from_union([from_str, from_none], obj.get("logPath"))
pid = from_union([from_int, from_none], obj.get("pid"))
- return TaskShellInfo(attachment_mode, command, description, id, started_at, status, type, can_promote_to_background, completed_at, execution_mode, log_path, pid)
+ return TaskShellInfo(attachment_mode, command, description, id, started_at, status, can_promote_to_background, completed_at, execution_mode, log_path, pid)
def to_dict(self) -> dict:
result: dict = {}
@@ -9938,7 +10024,7 @@ def to_dict(self) -> dict:
result["id"] = from_str(self.id)
result["startedAt"] = self.started_at.isoformat()
result["status"] = to_enum(TaskStatus, self.status)
- result["type"] = to_enum(TaskShellInfoType, self.type)
+ result["type"] = self.type
if self.can_promote_to_background is not None:
result["canPromoteToBackground"] = from_union([from_bool, from_none], self.can_promote_to_background)
if self.completed_at is not None:
@@ -9981,6 +10067,30 @@ def to_dict(self) -> dict:
result["pid"] = from_union([from_int, from_none], self.pid)
return result
+# Experimental: this type is part of an experimental API and may change or be removed.
+@dataclass
+class PermissionLocationAddToolApprovalParams:
+ """Location-scoped tool approval to persist."""
+
+ approval: PermissionsLocationsAddToolApprovalDetails
+ """Tool approval to persist and apply"""
+
+ location_key: str
+ """Location key (git root or cwd) to persist the approval to"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'PermissionLocationAddToolApprovalParams':
+ assert isinstance(obj, dict)
+ approval = _load_PermissionsLocationsAddToolApprovalDetails(obj.get("approval"))
+ location_key = from_str(obj.get("locationKey"))
+ return PermissionLocationAddToolApprovalParams(approval, location_key)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["approval"] = (self.approval).to_dict()
+ result["locationKey"] = from_str(self.location_key)
+ return result
+
@dataclass
class ToolList:
"""Built-in tools available for the requested model, with their parameters and instructions."""
@@ -10711,20 +10821,19 @@ class PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess:
extension_name: str
"""Extension name."""
- kind: PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind
+ kind: ClassVar[str] = "extension-permission-access"
"""Approval covering an extension's request to access a permission-gated capability."""
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess':
assert isinstance(obj, dict)
extension_name = from_str(obj.get("extensionName"))
- kind = PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind(obj.get("kind"))
- return PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess(extension_name, kind)
+ return PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess(extension_name)
def to_dict(self) -> dict:
result: dict = {}
result["extensionName"] = from_str(self.extension_name)
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind, self.kind)
+ result["kind"] = self.kind
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -10736,20 +10845,19 @@ class PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess:
extension_name: str
"""Extension name."""
- kind: PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind
+ kind: ClassVar[str] = "extension-permission-access"
"""Approval covering an extension's request to access a permission-gated capability."""
@staticmethod
def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess':
assert isinstance(obj, dict)
extension_name = from_str(obj.get("extensionName"))
- kind = PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind(obj.get("kind"))
- return PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess(extension_name, kind)
+ return PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess(extension_name)
def to_dict(self) -> dict:
result: dict = {}
result["extensionName"] = from_str(self.extension_name)
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind, self.kind)
+ result["kind"] = self.kind
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -10760,179 +10868,75 @@ class PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess:
extension_name: str
"""Extension name."""
- kind: PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind
+ kind: ClassVar[str] = "extension-permission-access"
"""Approval covering an extension's request to access a permission-gated capability."""
@staticmethod
def from_dict(obj: Any) -> 'PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess':
assert isinstance(obj, dict)
extension_name = from_str(obj.get("extensionName"))
- kind = PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind(obj.get("kind"))
- return PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess(extension_name, kind)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["extensionName"] = from_str(self.extension_name)
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind, self.kind)
- return result
-
-@dataclass
-class UserToolSessionApprovalExtensionManagement:
- """Schema for the `UserToolSessionApprovalExtensionManagement` type."""
-
- kind: PermissionDecisionApproveForLocationApprovalExtensionManagementKind
- """Extension management approval kind"""
-
- operation: str | None = None
- """Optional operation identifier"""
-
- @staticmethod
- def from_dict(obj: Any) -> 'UserToolSessionApprovalExtensionManagement':
- assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForLocationApprovalExtensionManagementKind(obj.get("kind"))
- operation = from_union([from_str, from_none], obj.get("operation"))
- return UserToolSessionApprovalExtensionManagement(kind, operation)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalExtensionManagementKind, self.kind)
- if self.operation is not None:
- result["operation"] = from_union([from_str, from_none], self.operation)
- return result
-
-@dataclass
-class UserToolSessionApprovalExtensionPermissionAccess:
- """Schema for the `UserToolSessionApprovalExtensionPermissionAccess` type."""
-
- extension_name: str
- """Extension name"""
-
- kind: PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind
- """Extension permission access approval kind"""
-
- @staticmethod
- def from_dict(obj: Any) -> 'UserToolSessionApprovalExtensionPermissionAccess':
- assert isinstance(obj, dict)
- extension_name = from_str(obj.get("extensionName"))
- kind = PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind(obj.get("kind"))
- return UserToolSessionApprovalExtensionPermissionAccess(extension_name, kind)
+ return PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess(extension_name)
def to_dict(self) -> dict:
result: dict = {}
result["extensionName"] = from_str(self.extension_name)
- result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind, self.kind)
+ result["kind"] = self.kind
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
-class ExternalToolTextResultForLlmContent:
- """A content block within a tool result, which may be text, terminal output, image, audio,
- or a resource
-
- Plain text content block
-
- Terminal/shell output content block with optional exit code and working directory
-
- Image content block with base64-encoded data
-
- Audio content block with base64-encoded data
-
- Resource link content block referencing an external resource
-
- Embedded resource content block with inline text or binary data
- """
- type: ExternalToolTextResultForLlmContentType
- """Content block type discriminator"""
-
- text: str | None = None
- """The text content
-
- Terminal/shell output text
- """
- cwd: str | None = None
- """Working directory where the command was executed"""
+class ExternalToolTextResultForLlm:
+ """Expanded external tool result payload"""
- exit_code: int | None = None
- """Process exit code, if the command has completed"""
+ text_result_for_llm: str
+ """Text result returned to the model"""
- data: str | None = None
- """Base64-encoded image data
+ binary_results_for_llm: list[ExternalToolTextResultForLlmBinaryResultsForLlm] | None = None
+ """Base64-encoded binary results returned to the model"""
- Base64-encoded audio data
- """
- mime_type: str | None = None
- """MIME type of the image (e.g., image/png, image/jpeg)
+ contents: list[ExternalToolTextResultForLlmContent] | None = None
+ """Structured content blocks from the tool"""
- MIME type of the audio (e.g., audio/wav, audio/mpeg)
+ error: str | None = None
+ """Optional error message for failed executions"""
- MIME type of the resource content
+ result_type: str | None = None
+ """Execution outcome classification. Optional for back-compat; normalized to 'success' (or
+ 'failure' when error is present) when missing or unrecognized.
"""
- description: str | None = None
- """Human-readable description of the resource"""
-
- icons: list[ExternalToolTextResultForLlmContentResourceLinkIcon] | None = None
- """Icons associated with this resource"""
-
- name: str | None = None
- """Resource name identifier"""
-
- size: int | None = None
- """Size of the resource in bytes"""
-
- title: str | None = None
- """Human-readable display title for the resource"""
-
- uri: str | None = None
- """URI identifying the resource"""
+ session_log: str | None = None
+ """Detailed log content for timeline display"""
- resource: ExternalToolTextResultForLlmContentResourceDetails | None = None
- """The embedded resource contents, either text or base64-encoded binary"""
+ tool_telemetry: dict[str, Any] | None = None
+ """Optional tool-specific telemetry"""
@staticmethod
- def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContent':
+ def from_dict(obj: Any) -> 'ExternalToolTextResultForLlm':
assert isinstance(obj, dict)
- type = ExternalToolTextResultForLlmContentType(obj.get("type"))
- text = from_union([from_str, from_none], obj.get("text"))
- cwd = from_union([from_str, from_none], obj.get("cwd"))
- exit_code = from_union([from_int, from_none], obj.get("exitCode"))
- data = from_union([from_str, from_none], obj.get("data"))
- mime_type = from_union([from_str, from_none], obj.get("mimeType"))
- description = from_union([from_str, from_none], obj.get("description"))
- icons = from_union([lambda x: from_list(ExternalToolTextResultForLlmContentResourceLinkIcon.from_dict, x), from_none], obj.get("icons"))
- name = from_union([from_str, from_none], obj.get("name"))
- size = from_union([from_int, from_none], obj.get("size"))
- title = from_union([from_str, from_none], obj.get("title"))
- uri = from_union([from_str, from_none], obj.get("uri"))
- resource = from_union([(lambda x: from_union([EmbeddedTextResourceContents.from_dict, EmbeddedBlobResourceContents.from_dict], x)), from_none], obj.get("resource"))
- return ExternalToolTextResultForLlmContent(type, text, cwd, exit_code, data, mime_type, description, icons, name, size, title, uri, resource)
+ text_result_for_llm = from_str(obj.get("textResultForLlm"))
+ binary_results_for_llm = from_union([lambda x: from_list(ExternalToolTextResultForLlmBinaryResultsForLlm.from_dict, x), from_none], obj.get("binaryResultsForLlm"))
+ contents = from_union([lambda x: from_list(_load_ExternalToolTextResultForLlmContent, x), from_none], obj.get("contents"))
+ error = from_union([from_str, from_none], obj.get("error"))
+ result_type = from_union([from_str, from_none], obj.get("resultType"))
+ session_log = from_union([from_str, from_none], obj.get("sessionLog"))
+ tool_telemetry = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("toolTelemetry"))
+ return ExternalToolTextResultForLlm(text_result_for_llm, binary_results_for_llm, contents, error, result_type, session_log, tool_telemetry)
def to_dict(self) -> dict:
result: dict = {}
- result["type"] = to_enum(ExternalToolTextResultForLlmContentType, self.type)
- if self.text is not None:
- result["text"] = from_union([from_str, from_none], self.text)
- if self.cwd is not None:
- result["cwd"] = from_union([from_str, from_none], self.cwd)
- if self.exit_code is not None:
- result["exitCode"] = from_union([from_int, from_none], self.exit_code)
- if self.data is not None:
- result["data"] = from_union([from_str, from_none], self.data)
- if self.mime_type is not None:
- result["mimeType"] = from_union([from_str, from_none], self.mime_type)
- if self.description is not None:
- result["description"] = from_union([from_str, from_none], self.description)
- if self.icons is not None:
- result["icons"] = from_union([lambda x: from_list(lambda x: to_class(ExternalToolTextResultForLlmContentResourceLinkIcon, x), x), from_none], self.icons)
- if self.name is not None:
- result["name"] = from_union([from_str, from_none], self.name)
- if self.size is not None:
- result["size"] = from_union([from_int, from_none], self.size)
- if self.title is not None:
- result["title"] = from_union([from_str, from_none], self.title)
- if self.uri is not None:
- result["uri"] = from_union([from_str, from_none], self.uri)
- if self.resource is not None:
- result["resource"] = from_union([lambda x: from_union([lambda x: to_class(EmbeddedTextResourceContents, x), lambda x: to_class(EmbeddedBlobResourceContents, x)], x), from_none], self.resource)
+ result["textResultForLlm"] = from_str(self.text_result_for_llm)
+ if self.binary_results_for_llm is not None:
+ result["binaryResultsForLlm"] = from_union([lambda x: from_list(lambda x: to_class(ExternalToolTextResultForLlmBinaryResultsForLlm, x), x), from_none], self.binary_results_for_llm)
+ if self.contents is not None:
+ result["contents"] = from_union([lambda x: from_list(lambda x: (x).to_dict(), x), from_none], self.contents)
+ if self.error is not None:
+ result["error"] = from_union([from_str, from_none], self.error)
+ if self.result_type is not None:
+ result["resultType"] = from_union([from_str, from_none], self.result_type)
+ if self.session_log is not None:
+ result["sessionLog"] = from_union([from_str, from_none], self.session_log)
+ if self.tool_telemetry is not None:
+ result["toolTelemetry"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.tool_telemetry)
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -10943,7 +10947,7 @@ class ExternalToolTextResultForLlmContentResourceLink:
name: str
"""Resource name identifier"""
- type: ExternalToolTextResultForLlmContentResourceLinkType
+ type: ClassVar[str] = "resource_link"
"""Content block type discriminator"""
uri: str
@@ -10968,19 +10972,18 @@ class ExternalToolTextResultForLlmContentResourceLink:
def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentResourceLink':
assert isinstance(obj, dict)
name = from_str(obj.get("name"))
- type = ExternalToolTextResultForLlmContentResourceLinkType(obj.get("type"))
uri = from_str(obj.get("uri"))
description = from_union([from_str, from_none], obj.get("description"))
icons = from_union([lambda x: from_list(ExternalToolTextResultForLlmContentResourceLinkIcon.from_dict, x), from_none], obj.get("icons"))
mime_type = from_union([from_str, from_none], obj.get("mimeType"))
size = from_union([from_int, from_none], obj.get("size"))
title = from_union([from_str, from_none], obj.get("title"))
- return ExternalToolTextResultForLlmContentResourceLink(name, type, uri, description, icons, mime_type, size, title)
+ return ExternalToolTextResultForLlmContentResourceLink(name, uri, description, icons, mime_type, size, title)
def to_dict(self) -> dict:
result: dict = {}
result["name"] = from_str(self.name)
- result["type"] = to_enum(ExternalToolTextResultForLlmContentResourceLinkType, self.type)
+ result["type"] = self.type
result["uri"] = from_str(self.uri)
if self.description is not None:
result["description"] = from_union([from_str, from_none], self.description)
@@ -11509,144 +11512,8 @@ def to_dict(self) -> dict:
# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
-class CommandsRespondToQueuedCommandRequest:
- """Queued-command request ID and the result indicating whether the host executed it (and
- whether to stop processing further queued commands).
- """
- request_id: str
- """Request ID from the `command.queued` event the host is responding to."""
-
- result: QueuedCommandResult
- """Result of the queued command execution."""
-
- @staticmethod
- def from_dict(obj: Any) -> 'CommandsRespondToQueuedCommandRequest':
- assert isinstance(obj, dict)
- request_id = from_str(obj.get("requestId"))
- result = QueuedCommandResult.from_dict(obj.get("result"))
- return CommandsRespondToQueuedCommandRequest(request_id, result)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["requestId"] = from_str(self.request_id)
- result["result"] = to_class(QueuedCommandResult, self.result)
- return result
-
-# Experimental: this type is part of an experimental API and may change or be removed.
-@dataclass
-class SendAttachment:
- """A user message attachment — a file, directory, code selection, blob, or GitHub reference
-
- File attachment
-
- Directory attachment
-
- Code selection attachment from an editor
-
- GitHub issue, pull request, or discussion reference
-
- Blob attachment with inline base64-encoded data
- """
- type: SendAttachmentType
- """Attachment type discriminator"""
-
- display_name: str | None = None
- """User-facing display name for the attachment
-
- User-facing display name for the selection
- """
- line_range: SendAttachmentFileLineRange | None = None
- """Optional line range to scope the attachment to a specific section of the file"""
-
- path: str | None = None
- """Absolute file path
-
- Absolute directory path
- """
- file_path: str | None = None
- """Absolute path to the file containing the selection"""
-
- selection: SendAttachmentSelectionDetails | None = None
- """Position range of the selection within the file"""
-
- text: str | None = None
- """The selected text content"""
-
- number: int | None = None
- """Issue, pull request, or discussion number"""
-
- reference_type: SendAttachmentGithubReferenceTypeEnum | None = None
- """Type of GitHub reference"""
-
- state: str | None = None
- """Current state of the referenced item (e.g., open, closed, merged)"""
-
- title: str | None = None
- """Title of the referenced item"""
-
- url: str | None = None
- """URL to the referenced item on GitHub"""
-
- data: str | None = None
- """Base64-encoded content"""
-
- mime_type: str | None = None
- """MIME type of the inline data"""
-
- @staticmethod
- def from_dict(obj: Any) -> 'SendAttachment':
- assert isinstance(obj, dict)
- type = SendAttachmentType(obj.get("type"))
- display_name = from_union([from_str, from_none], obj.get("displayName"))
- line_range = from_union([SendAttachmentFileLineRange.from_dict, from_none], obj.get("lineRange"))
- path = from_union([from_str, from_none], obj.get("path"))
- file_path = from_union([from_str, from_none], obj.get("filePath"))
- selection = from_union([SendAttachmentSelectionDetails.from_dict, from_none], obj.get("selection"))
- text = from_union([from_str, from_none], obj.get("text"))
- number = from_union([from_int, from_none], obj.get("number"))
- reference_type = from_union([SendAttachmentGithubReferenceTypeEnum, from_none], obj.get("referenceType"))
- state = from_union([from_str, from_none], obj.get("state"))
- title = from_union([from_str, from_none], obj.get("title"))
- url = from_union([from_str, from_none], obj.get("url"))
- data = from_union([from_str, from_none], obj.get("data"))
- mime_type = from_union([from_str, from_none], obj.get("mimeType"))
- return SendAttachment(type, display_name, line_range, path, file_path, selection, text, number, reference_type, state, title, url, data, mime_type)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["type"] = to_enum(SendAttachmentType, self.type)
- if self.display_name is not None:
- result["displayName"] = from_union([from_str, from_none], self.display_name)
- if self.line_range is not None:
- result["lineRange"] = from_union([lambda x: to_class(SendAttachmentFileLineRange, x), from_none], self.line_range)
- if self.path is not None:
- result["path"] = from_union([from_str, from_none], self.path)
- if self.file_path is not None:
- result["filePath"] = from_union([from_str, from_none], self.file_path)
- if self.selection is not None:
- result["selection"] = from_union([lambda x: to_class(SendAttachmentSelectionDetails, x), from_none], self.selection)
- if self.text is not None:
- result["text"] = from_union([from_str, from_none], self.text)
- if self.number is not None:
- result["number"] = from_union([from_int, from_none], self.number)
- if self.reference_type is not None:
- result["referenceType"] = from_union([lambda x: to_enum(SendAttachmentGithubReferenceTypeEnum, x), from_none], self.reference_type)
- if self.state is not None:
- result["state"] = from_union([from_str, from_none], self.state)
- if self.title is not None:
- result["title"] = from_union([from_str, from_none], self.title)
- if self.url is not None:
- result["url"] = from_union([from_str, from_none], self.url)
- if self.data is not None:
- result["data"] = from_union([from_str, from_none], self.data)
- if self.mime_type is not None:
- result["mimeType"] = from_union([from_str, from_none], self.mime_type)
- return result
-
-# Experimental: this type is part of an experimental API and may change or be removed.
-@dataclass
-class SendAttachmentSelection:
- """Code selection attachment from an editor"""
+class SendAttachmentSelection:
+ """Code selection attachment from an editor"""
display_name: str
"""User-facing display name for the selection"""
@@ -11660,7 +11527,7 @@ class SendAttachmentSelection:
text: str
"""The selected text content"""
- type: SendAttachmentSelectionType
+ type: ClassVar[str] = "selection"
"""Attachment type discriminator"""
@staticmethod
@@ -11670,8 +11537,7 @@ def from_dict(obj: Any) -> 'SendAttachmentSelection':
file_path = from_str(obj.get("filePath"))
selection = SendAttachmentSelectionDetails.from_dict(obj.get("selection"))
text = from_str(obj.get("text"))
- type = SendAttachmentSelectionType(obj.get("type"))
- return SendAttachmentSelection(display_name, file_path, selection, text, type)
+ return SendAttachmentSelection(display_name, file_path, selection, text)
def to_dict(self) -> dict:
result: dict = {}
@@ -11679,7 +11545,7 @@ def to_dict(self) -> dict:
result["filePath"] = from_str(self.file_path)
result["selection"] = to_class(SendAttachmentSelectionDetails, self.selection)
result["text"] = from_str(self.text)
- result["type"] = to_enum(SendAttachmentSelectionType, self.type)
+ result["type"] = self.type
return result
@dataclass
@@ -11917,107 +11783,6 @@ def to_dict(self) -> dict:
result["agent"] = to_class(AgentInfo, self.agent)
return result
-# Experimental: this type is part of an experimental API and may change or be removed.
-@dataclass
-class SlashCommandInvocationResult:
- """Result of invoking the slash command (text output, prompt to send to the agent, or
- completion).
-
- Schema for the `SlashCommandTextResult` type.
-
- Schema for the `SlashCommandAgentPromptResult` type.
-
- Schema for the `SlashCommandCompletedResult` type.
-
- Schema for the `SlashCommandSelectSubcommandResult` type.
- """
- kind: SlashCommandInvocationResultKind
- """Text result discriminator
-
- Agent prompt result discriminator
-
- Completed result discriminator
-
- Select subcommand result discriminator
- """
- markdown: bool | None = None
- """Whether text contains Markdown"""
-
- preserve_ansi: bool | None = None
- """Whether ANSI sequences should be preserved"""
-
- runtime_settings_changed: bool | None = None
- """True when the invocation mutated user runtime settings; consumers caching settings should
- refresh
- """
- text: str | None = None
- """Text output for the client to render"""
-
- display_prompt: str | None = None
- """Prompt text to display to the user"""
-
- mode: SessionMode | None = None
- """Optional target session mode for the agent prompt"""
-
- prompt: str | None = None
- """Prompt to submit to the agent"""
-
- message: str | None = None
- """Optional user-facing message describing the completed command"""
-
- command: str | None = None
- """Parent command name that requires subcommand selection"""
-
- options: list[SlashCommandSelectSubcommandOption] | None = None
- """Available subcommand options for the client to present"""
-
- title: str | None = None
- """Human-readable title for the selection UI"""
-
- @staticmethod
- def from_dict(obj: Any) -> 'SlashCommandInvocationResult':
- assert isinstance(obj, dict)
- kind = SlashCommandInvocationResultKind(obj.get("kind"))
- markdown = from_union([from_bool, from_none], obj.get("markdown"))
- preserve_ansi = from_union([from_bool, from_none], obj.get("preserveAnsi"))
- runtime_settings_changed = from_union([from_bool, from_none], obj.get("runtimeSettingsChanged"))
- text = from_union([from_str, from_none], obj.get("text"))
- display_prompt = from_union([from_str, from_none], obj.get("displayPrompt"))
- mode = from_union([SessionMode, from_none], obj.get("mode"))
- prompt = from_union([from_str, from_none], obj.get("prompt"))
- message = from_union([from_str, from_none], obj.get("message"))
- command = from_union([from_str, from_none], obj.get("command"))
- options = from_union([lambda x: from_list(SlashCommandSelectSubcommandOption.from_dict, x), from_none], obj.get("options"))
- title = from_union([from_str, from_none], obj.get("title"))
- return SlashCommandInvocationResult(kind, markdown, preserve_ansi, runtime_settings_changed, text, display_prompt, mode, prompt, message, command, options, title)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["kind"] = to_enum(SlashCommandInvocationResultKind, self.kind)
- if self.markdown is not None:
- result["markdown"] = from_union([from_bool, from_none], self.markdown)
- if self.preserve_ansi is not None:
- result["preserveAnsi"] = from_union([from_bool, from_none], self.preserve_ansi)
- if self.runtime_settings_changed is not None:
- result["runtimeSettingsChanged"] = from_union([from_bool, from_none], self.runtime_settings_changed)
- if self.text is not None:
- result["text"] = from_union([from_str, from_none], self.text)
- if self.display_prompt is not None:
- result["displayPrompt"] = from_union([from_str, from_none], self.display_prompt)
- if self.mode is not None:
- result["mode"] = from_union([lambda x: to_enum(SessionMode, x), from_none], self.mode)
- if self.prompt is not None:
- result["prompt"] = from_union([from_str, from_none], self.prompt)
- if self.message is not None:
- result["message"] = from_union([from_str, from_none], self.message)
- if self.command is not None:
- result["command"] = from_union([from_str, from_none], self.command)
- if self.options is not None:
- result["options"] = from_union([lambda x: from_list(lambda x: to_class(SlashCommandSelectSubcommandOption, x), x), from_none], self.options)
- if self.title is not None:
- result["title"] = from_union([from_str, from_none], self.title)
- return result
-
# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
class TaskProgress:
@@ -12451,7 +12216,7 @@ class APIKeyAuthInfo:
host: str
"""Authentication host."""
- type: APIKeyAuthInfoType
+ type: ClassVar[str] = "api-key"
"""API-key authentication for non-GitHub LLM providers (e.g. when running BYOM-style)."""
copilot_user: CopilotUserResponse | None = None
@@ -12465,15 +12230,14 @@ def from_dict(obj: Any) -> 'APIKeyAuthInfo':
assert isinstance(obj, dict)
api_key = from_str(obj.get("apiKey"))
host = from_str(obj.get("host"))
- type = APIKeyAuthInfoType(obj.get("type"))
copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser"))
- return APIKeyAuthInfo(api_key, host, type, copilot_user)
+ return APIKeyAuthInfo(api_key, host, copilot_user)
def to_dict(self) -> dict:
result: dict = {}
result["apiKey"] = from_str(self.api_key)
result["host"] = from_str(self.host)
- result["type"] = to_enum(APIKeyAuthInfoType, self.type)
+ result["type"] = self.type
if self.copilot_user is not None:
result["copilotUser"] = from_union([lambda x: to_class(CopilotUserResponse, x), from_none], self.copilot_user)
return result
@@ -12486,7 +12250,7 @@ class CopilotAPITokenAuthInfo:
host: Host
"""Authentication host (always the public GitHub host)."""
- type: CopilotAPITokenAuthInfoType
+ type: ClassVar[str] = "copilot-api-token"
"""Direct Copilot API authentication via the `GITHUB_COPILOT_API_TOKEN` + `COPILOT_API_URL`
environment-variable pair. The token itself is read from the environment by the runtime,
not carried in this struct.
@@ -12501,14 +12265,13 @@ class CopilotAPITokenAuthInfo:
def from_dict(obj: Any) -> 'CopilotAPITokenAuthInfo':
assert isinstance(obj, dict)
host = Host(obj.get("host"))
- type = CopilotAPITokenAuthInfoType(obj.get("type"))
copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser"))
- return CopilotAPITokenAuthInfo(host, type, copilot_user)
+ return CopilotAPITokenAuthInfo(host, copilot_user)
def to_dict(self) -> dict:
result: dict = {}
result["host"] = to_enum(Host, self.host)
- result["type"] = to_enum(CopilotAPITokenAuthInfoType, self.type)
+ result["type"] = self.type
if self.copilot_user is not None:
result["copilotUser"] = from_union([lambda x: to_class(CopilotUserResponse, x), from_none], self.copilot_user)
return result
@@ -12527,7 +12290,7 @@ class EnvAuthInfo:
token: str
"""The token value itself. Treat as a secret."""
- type: EnvAuthInfoType
+ type: ClassVar[str] = "env"
"""Personal access token (PAT) or server-to-server token sourced from an environment
variable.
"""
@@ -12547,17 +12310,16 @@ def from_dict(obj: Any) -> 'EnvAuthInfo':
env_var = from_str(obj.get("envVar"))
host = from_str(obj.get("host"))
token = from_str(obj.get("token"))
- type = EnvAuthInfoType(obj.get("type"))
copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser"))
login = from_union([from_str, from_none], obj.get("login"))
- return EnvAuthInfo(env_var, host, token, type, copilot_user, login)
+ return EnvAuthInfo(env_var, host, token, copilot_user, login)
def to_dict(self) -> dict:
result: dict = {}
result["envVar"] = from_str(self.env_var)
result["host"] = from_str(self.host)
result["token"] = from_str(self.token)
- result["type"] = to_enum(EnvAuthInfoType, self.type)
+ result["type"] = self.type
if self.copilot_user is not None:
result["copilotUser"] = from_union([lambda x: to_class(CopilotUserResponse, x), from_none], self.copilot_user)
if self.login is not None:
@@ -12578,7 +12340,7 @@ class GhCLIAuthInfo:
token: str
"""The token returned by `gh auth token`. Treat as a secret."""
- type: GhCLIAuthInfoType
+ type: ClassVar[str] = "gh-cli"
"""Authentication via the `gh` CLI's saved credentials."""
copilot_user: CopilotUserResponse | None = None
@@ -12593,16 +12355,15 @@ def from_dict(obj: Any) -> 'GhCLIAuthInfo':
host = from_str(obj.get("host"))
login = from_str(obj.get("login"))
token = from_str(obj.get("token"))
- type = GhCLIAuthInfoType(obj.get("type"))
copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser"))
- return GhCLIAuthInfo(host, login, token, type, copilot_user)
+ return GhCLIAuthInfo(host, login, token, copilot_user)
def to_dict(self) -> dict:
result: dict = {}
result["host"] = from_str(self.host)
result["login"] = from_str(self.login)
result["token"] = from_str(self.token)
- result["type"] = to_enum(GhCLIAuthInfoType, self.type)
+ result["type"] = self.type
if self.copilot_user is not None:
result["copilotUser"] = from_union([lambda x: to_class(CopilotUserResponse, x), from_none], self.copilot_user)
return result
@@ -12618,7 +12379,7 @@ class HMACAuthInfo:
host: Host
"""Authentication host. HMAC auth always targets the public GitHub host."""
- type: HMACAuthInfoType
+ type: ClassVar[str] = "hmac"
"""HMAC-based authentication used by GitHub-internal services."""
copilot_user: CopilotUserResponse | None = None
@@ -12632,15 +12393,14 @@ def from_dict(obj: Any) -> 'HMACAuthInfo':
assert isinstance(obj, dict)
hmac = from_str(obj.get("hmac"))
host = Host(obj.get("host"))
- type = HMACAuthInfoType(obj.get("type"))
copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser"))
- return HMACAuthInfo(hmac, host, type, copilot_user)
+ return HMACAuthInfo(hmac, host, copilot_user)
def to_dict(self) -> dict:
result: dict = {}
result["hmac"] = from_str(self.hmac)
result["host"] = to_enum(Host, self.host)
- result["type"] = to_enum(HMACAuthInfoType, self.type)
+ result["type"] = self.type
if self.copilot_user is not None:
result["copilotUser"] = from_union([lambda x: to_class(CopilotUserResponse, x), from_none], self.copilot_user)
return result
@@ -12656,7 +12416,7 @@ class TokenAuthInfo:
token: str
"""The token value itself. Treat as a secret."""
- type: TokenAuthInfoType
+ type: ClassVar[str] = "token"
"""SDK-side token authentication; the host configured the token directly via the SDK."""
copilot_user: CopilotUserResponse | None = None
@@ -12670,15 +12430,14 @@ def from_dict(obj: Any) -> 'TokenAuthInfo':
assert isinstance(obj, dict)
host = from_str(obj.get("host"))
token = from_str(obj.get("token"))
- type = TokenAuthInfoType(obj.get("type"))
copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser"))
- return TokenAuthInfo(host, token, type, copilot_user)
+ return TokenAuthInfo(host, token, copilot_user)
def to_dict(self) -> dict:
result: dict = {}
result["host"] = from_str(self.host)
result["token"] = from_str(self.token)
- result["type"] = to_enum(TokenAuthInfoType, self.type)
+ result["type"] = self.type
if self.copilot_user is not None:
result["copilotUser"] = from_union([lambda x: to_class(CopilotUserResponse, x), from_none], self.copilot_user)
return result
@@ -12694,7 +12453,7 @@ class UserAuthInfo:
login: str
"""OAuth user login."""
- type: UserAuthInfoType
+ type: ClassVar[str] = "user"
"""OAuth user authentication. The token itself is held in the runtime's secret token store
(keyed by host+login) and is NOT carried in this struct.
"""
@@ -12709,106 +12468,18 @@ def from_dict(obj: Any) -> 'UserAuthInfo':
assert isinstance(obj, dict)
host = from_str(obj.get("host"))
login = from_str(obj.get("login"))
- type = UserAuthInfoType(obj.get("type"))
copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser"))
- return UserAuthInfo(host, login, type, copilot_user)
+ return UserAuthInfo(host, login, copilot_user)
def to_dict(self) -> dict:
result: dict = {}
result["host"] = from_str(self.host)
result["login"] = from_str(self.login)
- result["type"] = to_enum(UserAuthInfoType, self.type)
+ result["type"] = self.type
if self.copilot_user is not None:
result["copilotUser"] = from_union([lambda x: to_class(CopilotUserResponse, x), from_none], self.copilot_user)
return result
-# Experimental: this type is part of an experimental API and may change or be removed.
-@dataclass
-class PermissionDecisionApproveForLocationApproval:
- """Approval to persist for this location
-
- Schema for the `PermissionDecisionApproveForLocationApprovalCommands` type.
-
- Schema for the `PermissionDecisionApproveForLocationApprovalRead` type.
-
- Schema for the `PermissionDecisionApproveForLocationApprovalWrite` type.
-
- Schema for the `PermissionDecisionApproveForLocationApprovalMcp` type.
-
- Schema for the `PermissionDecisionApproveForLocationApprovalMcpSampling` type.
-
- Schema for the `PermissionDecisionApproveForLocationApprovalMemory` type.
-
- Schema for the `PermissionDecisionApproveForLocationApprovalCustomTool` type.
-
- Schema for the `PermissionDecisionApproveForLocationApprovalExtensionManagement` type.
-
- Schema for the `PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess`
- type.
- """
- kind: ApprovalKind
- """Approval scoped to specific command identifiers.
-
- Approval covering read-only filesystem operations.
-
- Approval covering filesystem write operations.
-
- Approval covering an MCP tool.
-
- Approval covering MCP sampling requests for a server.
-
- Approval covering writes to long-term memory.
-
- Approval covering a custom tool.
-
- Approval covering extension lifecycle operations such as enable, disable, or reload.
-
- Approval covering an extension's request to access a permission-gated capability.
- """
- command_identifiers: list[str] | None = None
- """Command identifiers covered by this approval."""
-
- server_name: str | None = None
- """MCP server name."""
-
- tool_name: str | None = None
- """MCP tool name, or null to cover every tool on the server.
-
- Custom tool name.
- """
- operation: str | None = None
- """Optional operation identifier; when omitted, the approval covers all extension management
- operations.
- """
- extension_name: str | None = None
- """Extension name."""
-
- @staticmethod
- def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApproval':
- assert isinstance(obj, dict)
- kind = ApprovalKind(obj.get("kind"))
- command_identifiers = from_union([lambda x: from_list(from_str, x), from_none], obj.get("commandIdentifiers"))
- server_name = from_union([from_str, from_none], obj.get("serverName"))
- tool_name = from_union([from_none, from_str], obj.get("toolName"))
- operation = from_union([from_str, from_none], obj.get("operation"))
- extension_name = from_union([from_str, from_none], obj.get("extensionName"))
- return PermissionDecisionApproveForLocationApproval(kind, command_identifiers, server_name, tool_name, operation, extension_name)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["kind"] = to_enum(ApprovalKind, self.kind)
- if self.command_identifiers is not None:
- result["commandIdentifiers"] = from_union([lambda x: from_list(from_str, x), from_none], self.command_identifiers)
- if self.server_name is not None:
- result["serverName"] = from_union([from_str, from_none], self.server_name)
- if self.tool_name is not None:
- result["toolName"] = from_union([from_none, from_str], self.tool_name)
- if self.operation is not None:
- result["operation"] = from_union([from_str, from_none], self.operation)
- if self.extension_name is not None:
- result["extensionName"] = from_union([from_str, from_none], self.extension_name)
- return result
-
@dataclass
class PermissionDecisionApproveForIonApproval:
"""Session-scoped approval to remember (tool prompts only; omitted for path/url prompts)
@@ -12928,231 +12599,34 @@ def to_dict(self) -> dict:
# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
-class PermissionDecisionApproveForSessionApproval:
- """Session-scoped approval to remember (tool prompts only; omitted for path/url prompts)
-
- Schema for the `PermissionDecisionApproveForSessionApprovalCommands` type.
-
- Schema for the `PermissionDecisionApproveForSessionApprovalRead` type.
-
- Schema for the `PermissionDecisionApproveForSessionApprovalWrite` type.
-
- Schema for the `PermissionDecisionApproveForSessionApprovalMcp` type.
-
- Schema for the `PermissionDecisionApproveForSessionApprovalMcpSampling` type.
-
- Schema for the `PermissionDecisionApproveForSessionApprovalMemory` type.
-
- Schema for the `PermissionDecisionApproveForSessionApprovalCustomTool` type.
-
- Schema for the `PermissionDecisionApproveForSessionApprovalExtensionManagement` type.
-
- Schema for the `PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess`
- type.
- """
- kind: ApprovalKind
- """Approval scoped to specific command identifiers.
-
- Approval covering read-only filesystem operations.
-
- Approval covering filesystem write operations.
-
- Approval covering an MCP tool.
-
- Approval covering MCP sampling requests for a server.
-
- Approval covering writes to long-term memory.
-
- Approval covering a custom tool.
-
- Approval covering extension lifecycle operations such as enable, disable, or reload.
-
- Approval covering an extension's request to access a permission-gated capability.
- """
- command_identifiers: list[str] | None = None
- """Command identifiers covered by this approval."""
-
- server_name: str | None = None
- """MCP server name."""
-
- tool_name: str | None = None
- """MCP tool name, or null to cover every tool on the server.
-
- Custom tool name.
- """
- operation: str | None = None
- """Optional operation identifier; when omitted, the approval covers all extension management
- operations.
- """
- extension_name: str | None = None
- """Extension name."""
-
- @staticmethod
- def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApproval':
- assert isinstance(obj, dict)
- kind = ApprovalKind(obj.get("kind"))
- command_identifiers = from_union([lambda x: from_list(from_str, x), from_none], obj.get("commandIdentifiers"))
- server_name = from_union([from_str, from_none], obj.get("serverName"))
- tool_name = from_union([from_none, from_str], obj.get("toolName"))
- operation = from_union([from_str, from_none], obj.get("operation"))
- extension_name = from_union([from_str, from_none], obj.get("extensionName"))
- return PermissionDecisionApproveForSessionApproval(kind, command_identifiers, server_name, tool_name, operation, extension_name)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["kind"] = to_enum(ApprovalKind, self.kind)
- if self.command_identifiers is not None:
- result["commandIdentifiers"] = from_union([lambda x: from_list(from_str, x), from_none], self.command_identifiers)
- if self.server_name is not None:
- result["serverName"] = from_union([from_str, from_none], self.server_name)
- if self.tool_name is not None:
- result["toolName"] = from_union([from_none, from_str], self.tool_name)
- if self.operation is not None:
- result["operation"] = from_union([from_str, from_none], self.operation)
- if self.extension_name is not None:
- result["extensionName"] = from_union([from_str, from_none], self.extension_name)
- return result
-
-# Experimental: this type is part of an experimental API and may change or be removed.
-@dataclass
-class PermissionsLocationsAddToolApprovalDetails:
- """Tool approval to persist and apply
-
- Schema for the `PermissionsLocationsAddToolApprovalDetailsCommands` type.
-
- Schema for the `PermissionsLocationsAddToolApprovalDetailsRead` type.
-
- Schema for the `PermissionsLocationsAddToolApprovalDetailsWrite` type.
-
- Schema for the `PermissionsLocationsAddToolApprovalDetailsMcp` type.
-
- Schema for the `PermissionsLocationsAddToolApprovalDetailsMcpSampling` type.
-
- Schema for the `PermissionsLocationsAddToolApprovalDetailsMemory` type.
-
- Schema for the `PermissionsLocationsAddToolApprovalDetailsCustomTool` type.
-
- Schema for the `PermissionsLocationsAddToolApprovalDetailsExtensionManagement` type.
-
- Schema for the `PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess` type.
- """
- kind: ApprovalKind
- """Approval scoped to specific command identifiers.
-
- Approval covering read-only filesystem operations.
-
- Approval covering filesystem write operations.
-
- Approval covering an MCP tool.
-
- Approval covering MCP sampling requests for a server.
-
- Approval covering writes to long-term memory.
-
- Approval covering a custom tool.
-
- Approval covering extension lifecycle operations such as enable, disable, or reload.
-
- Approval covering an extension's request to access a permission-gated capability.
- """
- command_identifiers: list[str] | None = None
- """Command identifiers covered by this approval."""
-
- server_name: str | None = None
- """MCP server name."""
-
- tool_name: str | None = None
- """MCP tool name, or null to cover every tool on the server.
-
- Custom tool name.
- """
- operation: str | None = None
- """Optional operation identifier; when omitted, the approval covers all extension management
- operations.
- """
- extension_name: str | None = None
- """Extension name."""
-
- @staticmethod
- def from_dict(obj: Any) -> 'PermissionsLocationsAddToolApprovalDetails':
- assert isinstance(obj, dict)
- kind = ApprovalKind(obj.get("kind"))
- command_identifiers = from_union([lambda x: from_list(from_str, x), from_none], obj.get("commandIdentifiers"))
- server_name = from_union([from_str, from_none], obj.get("serverName"))
- tool_name = from_union([from_none, from_str], obj.get("toolName"))
- operation = from_union([from_str, from_none], obj.get("operation"))
- extension_name = from_union([from_str, from_none], obj.get("extensionName"))
- return PermissionsLocationsAddToolApprovalDetails(kind, command_identifiers, server_name, tool_name, operation, extension_name)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["kind"] = to_enum(ApprovalKind, self.kind)
- if self.command_identifiers is not None:
- result["commandIdentifiers"] = from_union([lambda x: from_list(from_str, x), from_none], self.command_identifiers)
- if self.server_name is not None:
- result["serverName"] = from_union([from_str, from_none], self.server_name)
- if self.tool_name is not None:
- result["toolName"] = from_union([from_none, from_str], self.tool_name)
- if self.operation is not None:
- result["operation"] = from_union([from_str, from_none], self.operation)
- if self.extension_name is not None:
- result["extensionName"] = from_union([from_str, from_none], self.extension_name)
- return result
-
-# Experimental: this type is part of an experimental API and may change or be removed.
-@dataclass
-class ExternalToolTextResultForLlm:
- """Expanded external tool result payload"""
-
- text_result_for_llm: str
- """Text result returned to the model"""
-
- binary_results_for_llm: list[ExternalToolTextResultForLlmBinaryResultsForLlm] | None = None
- """Base64-encoded binary results returned to the model"""
-
- contents: list[ExternalToolTextResultForLlmContent] | None = None
- """Structured content blocks from the tool"""
+class HandlePendingToolCallRequest:
+ """Pending external tool call request ID, with the tool result or an error describing why it
+ failed.
+ """
+ request_id: str
+ """Request ID of the pending tool call"""
error: str | None = None
- """Optional error message for failed executions"""
-
- result_type: str | None = None
- """Execution outcome classification. Optional for back-compat; normalized to 'success' (or
- 'failure' when error is present) when missing or unrecognized.
- """
- session_log: str | None = None
- """Detailed log content for timeline display"""
+ """Error message if the tool call failed"""
- tool_telemetry: dict[str, Any] | None = None
- """Optional tool-specific telemetry"""
+ result: ExternalToolTextResultForLlm | str | None = None
+ """Tool call result (string or expanded result object)"""
@staticmethod
- def from_dict(obj: Any) -> 'ExternalToolTextResultForLlm':
+ def from_dict(obj: Any) -> 'HandlePendingToolCallRequest':
assert isinstance(obj, dict)
- text_result_for_llm = from_str(obj.get("textResultForLlm"))
- binary_results_for_llm = from_union([lambda x: from_list(ExternalToolTextResultForLlmBinaryResultsForLlm.from_dict, x), from_none], obj.get("binaryResultsForLlm"))
- contents = from_union([lambda x: from_list(ExternalToolTextResultForLlmContent.from_dict, x), from_none], obj.get("contents"))
+ request_id = from_str(obj.get("requestId"))
error = from_union([from_str, from_none], obj.get("error"))
- result_type = from_union([from_str, from_none], obj.get("resultType"))
- session_log = from_union([from_str, from_none], obj.get("sessionLog"))
- tool_telemetry = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("toolTelemetry"))
- return ExternalToolTextResultForLlm(text_result_for_llm, binary_results_for_llm, contents, error, result_type, session_log, tool_telemetry)
+ result = from_union([ExternalToolTextResultForLlm.from_dict, from_str, from_none], obj.get("result"))
+ return HandlePendingToolCallRequest(request_id, error, result)
def to_dict(self) -> dict:
result: dict = {}
- result["textResultForLlm"] = from_str(self.text_result_for_llm)
- if self.binary_results_for_llm is not None:
- result["binaryResultsForLlm"] = from_union([lambda x: from_list(lambda x: to_class(ExternalToolTextResultForLlmBinaryResultsForLlm, x), x), from_none], self.binary_results_for_llm)
- if self.contents is not None:
- result["contents"] = from_union([lambda x: from_list(lambda x: to_class(ExternalToolTextResultForLlmContent, x), x), from_none], self.contents)
+ result["requestId"] = from_str(self.request_id)
if self.error is not None:
result["error"] = from_union([from_str, from_none], self.error)
- if self.result_type is not None:
- result["resultType"] = from_union([from_str, from_none], self.result_type)
- if self.session_log is not None:
- result["sessionLog"] = from_union([from_str, from_none], self.session_log)
- if self.tool_telemetry is not None:
- result["toolTelemetry"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.tool_telemetry)
+ if self.result is not None:
+ result["result"] = from_union([lambda x: to_class(ExternalToolTextResultForLlm, x), from_str, from_none], self.result)
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -13476,118 +12950,16 @@ def to_dict(self) -> dict:
# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
-class SendRequest:
- """Parameters for sending a user message to the session"""
-
- prompt: str
- """The user message text"""
-
- agent_mode: SendAgentMode | None = None
- """The UI mode the agent was in when this message was sent. Defaults to the session's
- current mode.
- """
- attachments: list[SendAttachment] | None = None
- """Optional attachments (files, directories, selections, blobs, GitHub references) to
- include with the message
- """
- billable: bool | None = None
- """If false, this message will not trigger a Premium Request Unit charge. User messages
- default to billable.
- """
- display_prompt: str | None = None
- """If provided, this is shown in the timeline instead of `prompt`"""
-
- mode: SendMode | None = None
- """How to deliver the message. `enqueue` (default) appends to the message queue. `immediate`
- interjects during an in-progress turn.
- """
- prepend: bool | None = None
- """If true, adds the message to the front of the queue instead of the end"""
-
- request_headers: dict[str, str] | None = None
- """Custom HTTP headers to include in outbound model requests for this turn. Merged with
- session-level provider headers; per-turn headers augment and overwrite session-level
- headers with the same key.
- """
- required_tool: str | None = None
- """If set, the request will fail if the named tool is not available when this message is
- among the user messages at the start of the current exchange
- """
- source: Any = None
- """Optional provenance tag copied to the resulting user.message event. Supported values are
- `system`, `command-*`, and `schedule-*`.
- """
- traceparent: str | None = None
- """W3C Trace Context traceparent header for distributed tracing of this agent turn"""
-
- tracestate: str | None = None
- """W3C Trace Context tracestate header for distributed tracing"""
+class TasksGetProgressResult:
+ """Progress information for the task, or null when no task with that ID is tracked."""
- wait: bool | None = None
- """If true, await completion of the agentic loop for this message before returning. Defaults
- to false (fire-and-forget). When true, the result still contains the same `messageId`;
- the caller can rely on the agent having processed the message before the call resolves.
+ progress: TaskProgress | None = None
+ """Progress information for the task, discriminated by type. Returns null when no task with
+ this ID is currently tracked.
"""
@staticmethod
- def from_dict(obj: Any) -> 'SendRequest':
- assert isinstance(obj, dict)
- prompt = from_str(obj.get("prompt"))
- agent_mode = from_union([SendAgentMode, from_none], obj.get("agentMode"))
- attachments = from_union([lambda x: from_list(SendAttachment.from_dict, x), from_none], obj.get("attachments"))
- billable = from_union([from_bool, from_none], obj.get("billable"))
- display_prompt = from_union([from_str, from_none], obj.get("displayPrompt"))
- mode = from_union([SendMode, from_none], obj.get("mode"))
- prepend = from_union([from_bool, from_none], obj.get("prepend"))
- request_headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("requestHeaders"))
- required_tool = from_union([from_str, from_none], obj.get("requiredTool"))
- source = obj.get("source")
- traceparent = from_union([from_str, from_none], obj.get("traceparent"))
- tracestate = from_union([from_str, from_none], obj.get("tracestate"))
- wait = from_union([from_bool, from_none], obj.get("wait"))
- return SendRequest(prompt, agent_mode, attachments, billable, display_prompt, mode, prepend, request_headers, required_tool, source, traceparent, tracestate, wait)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["prompt"] = from_str(self.prompt)
- if self.agent_mode is not None:
- result["agentMode"] = from_union([lambda x: to_enum(SendAgentMode, x), from_none], self.agent_mode)
- if self.attachments is not None:
- result["attachments"] = from_union([lambda x: from_list(lambda x: to_class(SendAttachment, x), x), from_none], self.attachments)
- if self.billable is not None:
- result["billable"] = from_union([from_bool, from_none], self.billable)
- if self.display_prompt is not None:
- result["displayPrompt"] = from_union([from_str, from_none], self.display_prompt)
- if self.mode is not None:
- result["mode"] = from_union([lambda x: to_enum(SendMode, x), from_none], self.mode)
- if self.prepend is not None:
- result["prepend"] = from_union([from_bool, from_none], self.prepend)
- if self.request_headers is not None:
- result["requestHeaders"] = from_union([lambda x: from_dict(from_str, x), from_none], self.request_headers)
- if self.required_tool is not None:
- result["requiredTool"] = from_union([from_str, from_none], self.required_tool)
- if self.source is not None:
- result["source"] = self.source
- if self.traceparent is not None:
- result["traceparent"] = from_union([from_str, from_none], self.traceparent)
- if self.tracestate is not None:
- result["tracestate"] = from_union([from_str, from_none], self.tracestate)
- if self.wait is not None:
- result["wait"] = from_union([from_bool, from_none], self.wait)
- return result
-
-# Experimental: this type is part of an experimental API and may change or be removed.
-@dataclass
-class TasksGetProgressResult:
- """Progress information for the task, or null when no task with that ID is tracked."""
-
- progress: TaskProgress | None = None
- """Progress information for the task, discriminated by type. Returns null when no task with
- this ID is currently tracked.
- """
-
- @staticmethod
- def from_dict(obj: Any) -> 'TasksGetProgressResult':
+ def from_dict(obj: Any) -> 'TasksGetProgressResult':
assert isinstance(obj, dict)
progress = from_union([TaskProgress.from_dict, from_none], obj.get("progress"))
return TasksGetProgressResult(progress)
@@ -13628,231 +13000,6 @@ def to_dict(self) -> dict:
result["required"] = from_union([lambda x: from_list(from_str, x), from_none], self.required)
return result
-# Experimental: this type is part of an experimental API and may change or be removed.
-@dataclass
-class AuthInfo:
- """The new auth credentials to install on the session. When omitted or `undefined`, the call
- is a no-op and the session's existing credentials are preserved. The runtime stores the
- value verbatim and uses it for outbound model/API requests; it does NOT re-validate or
- re-fetch the associated Copilot user response. Several variants carry secret material;
- treat this method's params as containing secrets at rest and in transit.
-
- Schema for the `HMACAuthInfo` type.
-
- Schema for the `EnvAuthInfo` type.
-
- Schema for the `TokenAuthInfo` type.
-
- Schema for the `CopilotApiTokenAuthInfo` type.
-
- Schema for the `UserAuthInfo` type.
-
- Schema for the `GhCliAuthInfo` type.
-
- Schema for the `ApiKeyAuthInfo` type.
- """
- host: str
- """Authentication host. HMAC auth always targets the public GitHub host.
-
- Authentication host (e.g. https://github.com or a GHES host).
-
- Authentication host.
-
- Authentication host (always the public GitHub host).
- """
- type: AuthInfoType
- """HMAC-based authentication used by GitHub-internal services.
-
- Personal access token (PAT) or server-to-server token sourced from an environment
- variable.
-
- SDK-side token authentication; the host configured the token directly via the SDK.
-
- Direct Copilot API authentication via the `GITHUB_COPILOT_API_TOKEN` + `COPILOT_API_URL`
- environment-variable pair. The token itself is read from the environment by the runtime,
- not carried in this struct.
-
- OAuth user authentication. The token itself is held in the runtime's secret token store
- (keyed by host+login) and is NOT carried in this struct.
-
- Authentication via the `gh` CLI's saved credentials.
-
- API-key authentication for non-GitHub LLM providers (e.g. when running BYOM-style).
- """
- copilot_user: CopilotUserResponse | None = None
- """Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the
- GitHub API `/copilot_internal/v2/token` user response shape — the runtime trusts this
- verbatim and does not re-fetch when set.
- """
- hmac: str | None = None
- """HMAC secret used to sign requests."""
-
- env_var: str | None = None
- """Name of the environment variable the token was sourced from."""
-
- login: str | None = None
- """User login associated with the token. Undefined for server-to-server tokens (those
- starting with `ghs_`).
-
- OAuth user login.
-
- User login as reported by `gh auth status`.
- """
- token: str | None = None
- """The token value itself. Treat as a secret.
-
- The token returned by `gh auth token`. Treat as a secret.
- """
- api_key: str | None = None
- """The API key. Treat as a secret."""
-
- @staticmethod
- def from_dict(obj: Any) -> 'AuthInfo':
- assert isinstance(obj, dict)
- host = from_str(obj.get("host"))
- type = AuthInfoType(obj.get("type"))
- copilot_user = from_union([CopilotUserResponse.from_dict, from_none], obj.get("copilotUser"))
- hmac = from_union([from_str, from_none], obj.get("hmac"))
- env_var = from_union([from_str, from_none], obj.get("envVar"))
- login = from_union([from_str, from_none], obj.get("login"))
- token = from_union([from_str, from_none], obj.get("token"))
- api_key = from_union([from_str, from_none], obj.get("apiKey"))
- return AuthInfo(host, type, copilot_user, hmac, env_var, login, token, api_key)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["host"] = from_str(self.host)
- result["type"] = to_enum(AuthInfoType, self.type)
- if self.copilot_user is not None:
- result["copilotUser"] = from_union([lambda x: to_class(CopilotUserResponse, x), from_none], self.copilot_user)
- if self.hmac is not None:
- result["hmac"] = from_union([from_str, from_none], self.hmac)
- if self.env_var is not None:
- result["envVar"] = from_union([from_str, from_none], self.env_var)
- if self.login is not None:
- result["login"] = from_union([from_str, from_none], self.login)
- if self.token is not None:
- result["token"] = from_union([from_str, from_none], self.token)
- if self.api_key is not None:
- result["apiKey"] = from_union([from_str, from_none], self.api_key)
- return result
-
-# Experimental: this type is part of an experimental API and may change or be removed.
-@dataclass
-class PermissionDecisionApproveForLocation:
- """Schema for the `PermissionDecisionApproveForLocation` type."""
-
- approval: PermissionDecisionApproveForLocationApproval
- """Approval to persist for this location"""
-
- kind: PermissionDecisionApproveForLocationKind
- """Approve and persist for this project location"""
-
- location_key: str
- """Location key (git root or cwd) to persist the approval to"""
-
- @staticmethod
- def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocation':
- assert isinstance(obj, dict)
- approval = PermissionDecisionApproveForLocationApproval.from_dict(obj.get("approval"))
- kind = PermissionDecisionApproveForLocationKind(obj.get("kind"))
- location_key = from_str(obj.get("locationKey"))
- return PermissionDecisionApproveForLocation(approval, kind, location_key)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["approval"] = to_class(PermissionDecisionApproveForLocationApproval, self.approval)
- result["kind"] = to_enum(PermissionDecisionApproveForLocationKind, self.kind)
- result["locationKey"] = from_str(self.location_key)
- return result
-
-# Experimental: this type is part of an experimental API and may change or be removed.
-@dataclass
-class PermissionDecisionApproveForSession:
- """Schema for the `PermissionDecisionApproveForSession` type."""
-
- kind: PermissionDecisionApproveForSessionKind
- """Approve and remember for the rest of the session"""
-
- approval: PermissionDecisionApproveForSessionApproval | None = None
- """Session-scoped approval to remember (tool prompts only; omitted for path/url prompts)"""
-
- domain: str | None = None
- """URL domain to approve for the rest of the session (URL prompts only)"""
-
- @staticmethod
- def from_dict(obj: Any) -> 'PermissionDecisionApproveForSession':
- assert isinstance(obj, dict)
- kind = PermissionDecisionApproveForSessionKind(obj.get("kind"))
- approval = from_union([PermissionDecisionApproveForSessionApproval.from_dict, from_none], obj.get("approval"))
- domain = from_union([from_str, from_none], obj.get("domain"))
- return PermissionDecisionApproveForSession(kind, approval, domain)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["kind"] = to_enum(PermissionDecisionApproveForSessionKind, self.kind)
- if self.approval is not None:
- result["approval"] = from_union([lambda x: to_class(PermissionDecisionApproveForSessionApproval, x), from_none], self.approval)
- if self.domain is not None:
- result["domain"] = from_union([from_str, from_none], self.domain)
- return result
-
-# Experimental: this type is part of an experimental API and may change or be removed.
-@dataclass
-class PermissionLocationAddToolApprovalParams:
- """Location-scoped tool approval to persist."""
-
- approval: PermissionsLocationsAddToolApprovalDetails
- """Tool approval to persist and apply"""
-
- location_key: str
- """Location key (git root or cwd) to persist the approval to"""
-
- @staticmethod
- def from_dict(obj: Any) -> 'PermissionLocationAddToolApprovalParams':
- assert isinstance(obj, dict)
- approval = PermissionsLocationsAddToolApprovalDetails.from_dict(obj.get("approval"))
- location_key = from_str(obj.get("locationKey"))
- return PermissionLocationAddToolApprovalParams(approval, location_key)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["approval"] = to_class(PermissionsLocationsAddToolApprovalDetails, self.approval)
- result["locationKey"] = from_str(self.location_key)
- return result
-
-# Experimental: this type is part of an experimental API and may change or be removed.
-@dataclass
-class HandlePendingToolCallRequest:
- """Pending external tool call request ID, with the tool result or an error describing why it
- failed.
- """
- request_id: str
- """Request ID of the pending tool call"""
-
- error: str | None = None
- """Error message if the tool call failed"""
-
- result: ExternalToolTextResultForLlm | str | None = None
- """Tool call result (string or expanded result object)"""
-
- @staticmethod
- def from_dict(obj: Any) -> 'HandlePendingToolCallRequest':
- assert isinstance(obj, dict)
- request_id = from_str(obj.get("requestId"))
- error = from_union([from_str, from_none], obj.get("error"))
- result = from_union([ExternalToolTextResultForLlm.from_dict, from_str, from_none], obj.get("result"))
- return HandlePendingToolCallRequest(request_id, error, result)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["requestId"] = from_str(self.request_id)
- if self.error is not None:
- result["error"] = from_union([from_str, from_none], self.error)
- if self.result is not None:
- result["result"] = from_union([lambda x: to_class(ExternalToolTextResultForLlm, x), from_str, from_none], self.result)
- return result
-
# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
class SessionsSetAdditionalPluginsRequest:
@@ -14078,263 +13225,63 @@ def to_dict(self) -> dict:
if self.feature_flags is not None:
result["featureFlags"] = from_union([lambda x: from_dict(from_bool, x), from_none], self.feature_flags)
if self.installed_plugins is not None:
- result["installedPlugins"] = from_union([lambda x: from_list(lambda x: to_class(SessionInstalledPlugin, x), x), from_none], self.installed_plugins)
- if self.integration_id is not None:
- result["integrationId"] = from_union([from_str, from_none], self.integration_id)
- if self.is_experimental_mode is not None:
- result["isExperimentalMode"] = from_union([from_bool, from_none], self.is_experimental_mode)
- if self.log_interactive_shells is not None:
- result["logInteractiveShells"] = from_union([from_bool, from_none], self.log_interactive_shells)
- if self.lsp_client_name is not None:
- result["lspClientName"] = from_union([from_str, from_none], self.lsp_client_name)
- if self.manage_schedule_enabled is not None:
- result["manageScheduleEnabled"] = from_union([from_bool, from_none], self.manage_schedule_enabled)
- if self.model is not None:
- result["model"] = from_union([from_str, from_none], self.model)
- if self.provider is not None:
- result["provider"] = self.provider
- if self.reasoning_effort is not None:
- result["reasoningEffort"] = from_union([from_str, from_none], self.reasoning_effort)
- if self.running_in_interactive_mode is not None:
- result["runningInInteractiveMode"] = from_union([from_bool, from_none], self.running_in_interactive_mode)
- if self.sandbox_config is not None:
- result["sandboxConfig"] = self.sandbox_config
- if self.shell_init_profile is not None:
- result["shellInitProfile"] = from_union([from_str, from_none], self.shell_init_profile)
- if self.shell_process_flags is not None:
- result["shellProcessFlags"] = from_union([lambda x: from_list(from_str, x), from_none], self.shell_process_flags)
- if self.skill_directories is not None:
- result["skillDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.skill_directories)
- if self.skip_custom_instructions is not None:
- result["skipCustomInstructions"] = from_union([from_bool, from_none], self.skip_custom_instructions)
- if self.trajectory_file is not None:
- result["trajectoryFile"] = from_union([from_str, from_none], self.trajectory_file)
- if self.working_directory is not None:
- result["workingDirectory"] = from_union([from_str, from_none], self.working_directory)
- return result
-
-# Experimental: this type is part of an experimental API and may change or be removed.
-@dataclass
-class UIElicitationRequest:
- """Prompt message and JSON schema describing the form fields to elicit from the user."""
-
- message: str
- """Message describing what information is needed from the user"""
-
- requested_schema: UIElicitationSchema
- """JSON Schema describing the form fields to present to the user"""
-
- @staticmethod
- def from_dict(obj: Any) -> 'UIElicitationRequest':
- assert isinstance(obj, dict)
- message = from_str(obj.get("message"))
- requested_schema = UIElicitationSchema.from_dict(obj.get("requestedSchema"))
- return UIElicitationRequest(message, requested_schema)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["message"] = from_str(self.message)
- result["requestedSchema"] = to_class(UIElicitationSchema, self.requested_schema)
- return result
-
-# Experimental: this type is part of an experimental API and may change or be removed.
-@dataclass
-class SessionSetCredentialsParams:
- """New auth credentials to install on the session. Omit to leave credentials unchanged."""
-
- credentials: AuthInfo | None = None
- """The new auth credentials to install on the session. When omitted or `undefined`, the call
- is a no-op and the session's existing credentials are preserved. The runtime stores the
- value verbatim and uses it for outbound model/API requests; it does NOT re-validate or
- re-fetch the associated Copilot user response. Several variants carry secret material;
- treat this method's params as containing secrets at rest and in transit.
- """
-
- @staticmethod
- def from_dict(obj: Any) -> 'SessionSetCredentialsParams':
- assert isinstance(obj, dict)
- credentials = from_union([AuthInfo.from_dict, from_none], obj.get("credentials"))
- return SessionSetCredentialsParams(credentials)
-
- def to_dict(self) -> dict:
- result: dict = {}
- if self.credentials is not None:
- result["credentials"] = from_union([lambda x: to_class(AuthInfo, x), from_none], self.credentials)
- return result
-
-# Experimental: this type is part of an experimental API and may change or be removed.
-@dataclass
-class PermissionDecision:
- """The client's response to the pending permission prompt
-
- Schema for the `PermissionDecisionApproveOnce` type.
-
- Schema for the `PermissionDecisionApproveForSession` type.
-
- Schema for the `PermissionDecisionApproveForLocation` type.
-
- Schema for the `PermissionDecisionApprovePermanently` type.
-
- Schema for the `PermissionDecisionReject` type.
-
- Schema for the `PermissionDecisionUserNotAvailable` type.
-
- Schema for the `PermissionDecisionApproved` type.
-
- Schema for the `PermissionDecisionApprovedForSession` type.
-
- Schema for the `PermissionDecisionApprovedForLocation` type.
-
- Schema for the `PermissionDecisionCancelled` type.
-
- Schema for the `PermissionDecisionDeniedByRules` type.
-
- Schema for the `PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser` type.
-
- Schema for the `PermissionDecisionDeniedInteractivelyByUser` type.
-
- Schema for the `PermissionDecisionDeniedByContentExclusionPolicy` type.
-
- Schema for the `PermissionDecisionDeniedByPermissionRequestHook` type.
- """
- kind: PermissionDecisionKind
- """Approve this single request only
-
- Approve and remember for the rest of the session
-
- Approve and persist for this project location
-
- Approve and persist across sessions (URL prompts only)
-
- Reject the request
-
- No user is available to confirm the request
-
- The permission request was approved
-
- Approved and remembered for the rest of the session
-
- Approved and persisted for this project location
-
- The permission request was cancelled before a response was used
-
- Denied because approval rules explicitly blocked it
-
- Denied because no approval rule matched and user confirmation was unavailable
-
- Denied by the user during an interactive prompt
-
- Denied by the organization's content exclusion policy
-
- Denied by a permission request hook registered by an extension or plugin
- """
- approval: PermissionDecisionApproveForIonApproval | None = None
- """Session-scoped approval to remember (tool prompts only; omitted for path/url prompts)
-
- Approval to persist for this location
-
- The approval to add as a session-scoped rule
-
- The approval to persist for this location
- """
- domain: str | None = None
- """URL domain to approve for the rest of the session (URL prompts only)
-
- URL domain to approve permanently
- """
- location_key: str | None = None
- """Location key (git root or cwd) to persist the approval to
-
- The location key (git root or cwd) to persist the approval to
- """
- feedback: str | None = None
- """Optional feedback explaining the rejection
-
- Optional feedback from the user explaining the denial
- """
- reason: str | None = None
- """Optional explanation of why the request was cancelled"""
-
- rules: list[PermissionRule] | None = None
- """Rules that denied the request"""
-
- force_reject: bool | None = None
- """Whether to force-reject the current agent turn"""
-
- message: str | None = None
- """Human-readable explanation of why the path was excluded
-
- Optional message from the hook explaining the denial
- """
- path: str | None = None
- """File path that triggered the exclusion"""
-
- interrupt: bool | None = None
- """Whether to interrupt the current agent turn"""
-
- @staticmethod
- def from_dict(obj: Any) -> 'PermissionDecision':
- assert isinstance(obj, dict)
- kind = PermissionDecisionKind(obj.get("kind"))
- approval = from_union([PermissionDecisionApproveForIonApproval.from_dict, from_none], obj.get("approval"))
- domain = from_union([from_str, from_none], obj.get("domain"))
- location_key = from_union([from_str, from_none], obj.get("locationKey"))
- feedback = from_union([from_str, from_none], obj.get("feedback"))
- reason = from_union([from_str, from_none], obj.get("reason"))
- rules = from_union([lambda x: from_list(PermissionRule.from_dict, x), from_none], obj.get("rules"))
- force_reject = from_union([from_bool, from_none], obj.get("forceReject"))
- message = from_union([from_str, from_none], obj.get("message"))
- path = from_union([from_str, from_none], obj.get("path"))
- interrupt = from_union([from_bool, from_none], obj.get("interrupt"))
- return PermissionDecision(kind, approval, domain, location_key, feedback, reason, rules, force_reject, message, path, interrupt)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["kind"] = to_enum(PermissionDecisionKind, self.kind)
- if self.approval is not None:
- result["approval"] = from_union([lambda x: to_class(PermissionDecisionApproveForIonApproval, x), from_none], self.approval)
- if self.domain is not None:
- result["domain"] = from_union([from_str, from_none], self.domain)
- if self.location_key is not None:
- result["locationKey"] = from_union([from_str, from_none], self.location_key)
- if self.feedback is not None:
- result["feedback"] = from_union([from_str, from_none], self.feedback)
- if self.reason is not None:
- result["reason"] = from_union([from_str, from_none], self.reason)
- if self.rules is not None:
- result["rules"] = from_union([lambda x: from_list(lambda x: to_class(PermissionRule, x), x), from_none], self.rules)
- if self.force_reject is not None:
- result["forceReject"] = from_union([from_bool, from_none], self.force_reject)
- if self.message is not None:
- result["message"] = from_union([from_str, from_none], self.message)
- if self.path is not None:
- result["path"] = from_union([from_str, from_none], self.path)
- if self.interrupt is not None:
- result["interrupt"] = from_union([from_bool, from_none], self.interrupt)
+ result["installedPlugins"] = from_union([lambda x: from_list(lambda x: to_class(SessionInstalledPlugin, x), x), from_none], self.installed_plugins)
+ if self.integration_id is not None:
+ result["integrationId"] = from_union([from_str, from_none], self.integration_id)
+ if self.is_experimental_mode is not None:
+ result["isExperimentalMode"] = from_union([from_bool, from_none], self.is_experimental_mode)
+ if self.log_interactive_shells is not None:
+ result["logInteractiveShells"] = from_union([from_bool, from_none], self.log_interactive_shells)
+ if self.lsp_client_name is not None:
+ result["lspClientName"] = from_union([from_str, from_none], self.lsp_client_name)
+ if self.manage_schedule_enabled is not None:
+ result["manageScheduleEnabled"] = from_union([from_bool, from_none], self.manage_schedule_enabled)
+ if self.model is not None:
+ result["model"] = from_union([from_str, from_none], self.model)
+ if self.provider is not None:
+ result["provider"] = self.provider
+ if self.reasoning_effort is not None:
+ result["reasoningEffort"] = from_union([from_str, from_none], self.reasoning_effort)
+ if self.running_in_interactive_mode is not None:
+ result["runningInInteractiveMode"] = from_union([from_bool, from_none], self.running_in_interactive_mode)
+ if self.sandbox_config is not None:
+ result["sandboxConfig"] = self.sandbox_config
+ if self.shell_init_profile is not None:
+ result["shellInitProfile"] = from_union([from_str, from_none], self.shell_init_profile)
+ if self.shell_process_flags is not None:
+ result["shellProcessFlags"] = from_union([lambda x: from_list(from_str, x), from_none], self.shell_process_flags)
+ if self.skill_directories is not None:
+ result["skillDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.skill_directories)
+ if self.skip_custom_instructions is not None:
+ result["skipCustomInstructions"] = from_union([from_bool, from_none], self.skip_custom_instructions)
+ if self.trajectory_file is not None:
+ result["trajectoryFile"] = from_union([from_str, from_none], self.trajectory_file)
+ if self.working_directory is not None:
+ result["workingDirectory"] = from_union([from_str, from_none], self.working_directory)
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@dataclass
-class PermissionDecisionRequest:
- """Pending permission request ID and the decision to apply (approve/reject and scope)."""
+class UIElicitationRequest:
+ """Prompt message and JSON schema describing the form fields to elicit from the user."""
- request_id: str
- """Request ID of the pending permission request"""
+ message: str
+ """Message describing what information is needed from the user"""
- result: PermissionDecision
- """The client's response to the pending permission prompt"""
+ requested_schema: UIElicitationSchema
+ """JSON Schema describing the form fields to present to the user"""
@staticmethod
- def from_dict(obj: Any) -> 'PermissionDecisionRequest':
+ def from_dict(obj: Any) -> 'UIElicitationRequest':
assert isinstance(obj, dict)
- request_id = from_str(obj.get("requestId"))
- result = PermissionDecision.from_dict(obj.get("result"))
- return PermissionDecisionRequest(request_id, result)
+ message = from_str(obj.get("message"))
+ requested_schema = UIElicitationSchema.from_dict(obj.get("requestedSchema"))
+ return UIElicitationRequest(message, requested_schema)
def to_dict(self) -> dict:
result: dict = {}
- result["requestId"] = from_str(self.request_id)
- result["result"] = to_class(PermissionDecision, self.result)
+ result["message"] = from_str(self.message)
+ result["requestedSchema"] = to_class(UIElicitationSchema, self.requested_schema)
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -14642,7 +13589,7 @@ class TaskAgentInfo:
tool_call_id: str
"""Tool call ID associated with this agent task"""
- type: TaskAgentInfoType
+ type: ClassVar[str] = "agent"
"""Task kind"""
active_started_at: datetime | None = None
@@ -14687,7 +13634,6 @@ def from_dict(obj: Any) -> 'TaskAgentInfo':
started_at = from_datetime(obj.get("startedAt"))
status = TaskStatus(obj.get("status"))
tool_call_id = from_str(obj.get("toolCallId"))
- type = TaskAgentInfoType(obj.get("type"))
active_started_at = from_union([from_datetime, from_none], obj.get("activeStartedAt"))
active_time_ms = from_union([from_int, from_none], obj.get("activeTimeMs"))
can_promote_to_background = from_union([from_bool, from_none], obj.get("canPromoteToBackground"))
@@ -14698,7 +13644,7 @@ def from_dict(obj: Any) -> 'TaskAgentInfo':
latest_response = from_union([from_str, from_none], obj.get("latestResponse"))
model = from_union([from_str, from_none], obj.get("model"))
result = from_union([from_str, from_none], obj.get("result"))
- return TaskAgentInfo(agent_type, description, id, prompt, started_at, status, tool_call_id, type, active_started_at, active_time_ms, can_promote_to_background, completed_at, error, execution_mode, idle_since, latest_response, model, result)
+ return TaskAgentInfo(agent_type, description, id, prompt, started_at, status, tool_call_id, active_started_at, active_time_ms, can_promote_to_background, completed_at, error, execution_mode, idle_since, latest_response, model, result)
def to_dict(self) -> dict:
result: dict = {}
@@ -14709,157 +13655,11 @@ def to_dict(self) -> dict:
result["startedAt"] = self.started_at.isoformat()
result["status"] = to_enum(TaskStatus, self.status)
result["toolCallId"] = from_str(self.tool_call_id)
- result["type"] = to_enum(TaskAgentInfoType, self.type)
- if self.active_started_at is not None:
- result["activeStartedAt"] = from_union([lambda x: x.isoformat(), from_none], self.active_started_at)
- if self.active_time_ms is not None:
- result["activeTimeMs"] = from_union([from_int, from_none], self.active_time_ms)
- if self.can_promote_to_background is not None:
- result["canPromoteToBackground"] = from_union([from_bool, from_none], self.can_promote_to_background)
- if self.completed_at is not None:
- result["completedAt"] = from_union([lambda x: x.isoformat(), from_none], self.completed_at)
- if self.error is not None:
- result["error"] = from_union([from_str, from_none], self.error)
- if self.execution_mode is not None:
- result["executionMode"] = from_union([lambda x: to_enum(TaskExecutionMode, x), from_none], self.execution_mode)
- if self.idle_since is not None:
- result["idleSince"] = from_union([lambda x: x.isoformat(), from_none], self.idle_since)
- if self.latest_response is not None:
- result["latestResponse"] = from_union([from_str, from_none], self.latest_response)
- if self.model is not None:
- result["model"] = from_union([from_str, from_none], self.model)
- if self.result is not None:
- result["result"] = from_union([from_str, from_none], self.result)
- return result
-
-# Experimental: this type is part of an experimental API and may change or be removed.
-@dataclass
-class TaskInfo:
- """Schema for the `TaskInfo` type.
-
- The first sync-waiting task (agent first, then shell) that can currently be promoted to
- background mode. Omitted if no such task exists. The returned task is guaranteed to have
- executionMode='sync' and canPromoteToBackground=true at the time of the call.
-
- The promoted task as it now exists in background mode, omitted if no promotable task was
- waiting. Atomic operation: avoids the race window of getCurrentPromotable +
- promoteToBackground.
-
- Schema for the `TaskAgentInfo` type.
-
- Schema for the `TaskShellInfo` type.
- """
- description: str
- """Short description of the task"""
-
- id: str
- """Unique task identifier"""
-
- started_at: datetime
- """ISO 8601 timestamp when the task was started"""
-
- status: TaskStatus
- """Current lifecycle status of the task"""
-
- type: TaskInfoType
- """Task kind"""
-
- active_started_at: datetime | None = None
- """ISO 8601 timestamp when the current active period began"""
-
- active_time_ms: int | None = None
- """Accumulated active execution time in milliseconds"""
-
- agent_type: str | None = None
- """Type of agent running this task"""
-
- can_promote_to_background: bool | None = None
- """Whether the task is currently in the original sync wait and can be moved to background
- mode. False once it is already backgrounded, idle, finished, or no longer has a
- promotable sync waiter.
-
- Whether this shell task can be promoted to background mode
- """
- completed_at: datetime | None = None
- """ISO 8601 timestamp when the task finished"""
-
- error: str | None = None
- """Error message when the task failed"""
-
- execution_mode: TaskExecutionMode | None = None
- """Whether task execution is synchronously awaited or managed in the background"""
-
- idle_since: datetime | None = None
- """ISO 8601 timestamp when the agent entered idle state"""
-
- latest_response: str | None = None
- """Most recent response text from the agent"""
-
- model: str | None = None
- """Model used for the task when specified"""
-
- prompt: str | None = None
- """Prompt passed to the agent"""
-
- result: str | None = None
- """Result text from the task when available"""
-
- tool_call_id: str | None = None
- """Tool call ID associated with this agent task"""
-
- attachment_mode: TaskShellInfoAttachmentMode | None = None
- """Whether the shell runs inside a managed PTY session or as an independent background
- process
- """
- command: str | None = None
- """Command being executed"""
-
- log_path: str | None = None
- """Path to the detached shell log, when available"""
-
- pid: int | None = None
- """Process ID when available"""
-
- @staticmethod
- def from_dict(obj: Any) -> 'TaskInfo':
- assert isinstance(obj, dict)
- description = from_str(obj.get("description"))
- id = from_str(obj.get("id"))
- started_at = from_datetime(obj.get("startedAt"))
- status = TaskStatus(obj.get("status"))
- type = TaskInfoType(obj.get("type"))
- active_started_at = from_union([from_datetime, from_none], obj.get("activeStartedAt"))
- active_time_ms = from_union([from_int, from_none], obj.get("activeTimeMs"))
- agent_type = from_union([from_str, from_none], obj.get("agentType"))
- can_promote_to_background = from_union([from_bool, from_none], obj.get("canPromoteToBackground"))
- completed_at = from_union([from_datetime, from_none], obj.get("completedAt"))
- error = from_union([from_str, from_none], obj.get("error"))
- execution_mode = from_union([TaskExecutionMode, from_none], obj.get("executionMode"))
- idle_since = from_union([from_datetime, from_none], obj.get("idleSince"))
- latest_response = from_union([from_str, from_none], obj.get("latestResponse"))
- model = from_union([from_str, from_none], obj.get("model"))
- prompt = from_union([from_str, from_none], obj.get("prompt"))
- result = from_union([from_str, from_none], obj.get("result"))
- tool_call_id = from_union([from_str, from_none], obj.get("toolCallId"))
- attachment_mode = from_union([TaskShellInfoAttachmentMode, from_none], obj.get("attachmentMode"))
- command = from_union([from_str, from_none], obj.get("command"))
- log_path = from_union([from_str, from_none], obj.get("logPath"))
- pid = from_union([from_int, from_none], obj.get("pid"))
- return TaskInfo(description, id, started_at, status, type, active_started_at, active_time_ms, agent_type, can_promote_to_background, completed_at, error, execution_mode, idle_since, latest_response, model, prompt, result, tool_call_id, attachment_mode, command, log_path, pid)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["description"] = from_str(self.description)
- result["id"] = from_str(self.id)
- result["startedAt"] = self.started_at.isoformat()
- result["status"] = to_enum(TaskStatus, self.status)
- result["type"] = to_enum(TaskInfoType, self.type)
+ result["type"] = self.type
if self.active_started_at is not None:
result["activeStartedAt"] = from_union([lambda x: x.isoformat(), from_none], self.active_started_at)
if self.active_time_ms is not None:
result["activeTimeMs"] = from_union([from_int, from_none], self.active_time_ms)
- if self.agent_type is not None:
- result["agentType"] = from_union([from_str, from_none], self.agent_type)
if self.can_promote_to_background is not None:
result["canPromoteToBackground"] = from_union([from_bool, from_none], self.can_promote_to_background)
if self.completed_at is not None:
@@ -14874,86 +13674,8 @@ def to_dict(self) -> dict:
result["latestResponse"] = from_union([from_str, from_none], self.latest_response)
if self.model is not None:
result["model"] = from_union([from_str, from_none], self.model)
- if self.prompt is not None:
- result["prompt"] = from_union([from_str, from_none], self.prompt)
if self.result is not None:
result["result"] = from_union([from_str, from_none], self.result)
- if self.tool_call_id is not None:
- result["toolCallId"] = from_union([from_str, from_none], self.tool_call_id)
- if self.attachment_mode is not None:
- result["attachmentMode"] = from_union([lambda x: to_enum(TaskShellInfoAttachmentMode, x), from_none], self.attachment_mode)
- if self.command is not None:
- result["command"] = from_union([from_str, from_none], self.command)
- if self.log_path is not None:
- result["logPath"] = from_union([from_str, from_none], self.log_path)
- if self.pid is not None:
- result["pid"] = from_union([from_int, from_none], self.pid)
- return result
-
-# Experimental: this type is part of an experimental API and may change or be removed.
-@dataclass
-class TaskList:
- """Background tasks currently tracked by the session."""
-
- tasks: list[TaskInfo]
- """Currently tracked tasks"""
-
- @staticmethod
- def from_dict(obj: Any) -> 'TaskList':
- assert isinstance(obj, dict)
- tasks = from_list(TaskInfo.from_dict, obj.get("tasks"))
- return TaskList(tasks)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["tasks"] = from_list(lambda x: to_class(TaskInfo, x), self.tasks)
- return result
-
-# Experimental: this type is part of an experimental API and may change or be removed.
-@dataclass
-class TasksGetCurrentPromotableResult:
- """The first sync-waiting task that can currently be promoted to background mode."""
-
- task: TaskInfo | None = None
- """The first sync-waiting task (agent first, then shell) that can currently be promoted to
- background mode. Omitted if no such task exists. The returned task is guaranteed to have
- executionMode='sync' and canPromoteToBackground=true at the time of the call.
- """
-
- @staticmethod
- def from_dict(obj: Any) -> 'TasksGetCurrentPromotableResult':
- assert isinstance(obj, dict)
- task = from_union([TaskInfo.from_dict, from_none], obj.get("task"))
- return TasksGetCurrentPromotableResult(task)
-
- def to_dict(self) -> dict:
- result: dict = {}
- if self.task is not None:
- result["task"] = from_union([lambda x: to_class(TaskInfo, x), from_none], self.task)
- return result
-
-# Experimental: this type is part of an experimental API and may change or be removed.
-@dataclass
-class TasksPromoteCurrentToBackgroundResult:
- """The promoted task as it now exists in background mode, omitted if no promotable task was
- waiting.
- """
- task: TaskInfo | None = None
- """The promoted task as it now exists in background mode, omitted if no promotable task was
- waiting. Atomic operation: avoids the race window of getCurrentPromotable +
- promoteToBackground.
- """
-
- @staticmethod
- def from_dict(obj: Any) -> 'TasksPromoteCurrentToBackgroundResult':
- assert isinstance(obj, dict)
- task = from_union([TaskInfo.from_dict, from_none], obj.get("task"))
- return TasksPromoteCurrentToBackgroundResult(task)
-
- def to_dict(self) -> dict:
- result: dict = {}
- if self.task is not None:
- result["task"] = from_union([lambda x: to_class(TaskInfo, x), from_none], self.task)
return result
@dataclass
@@ -15449,14 +14171,6 @@ class RPC:
usage_metrics_model_metric_usage: UsageMetricsModelMetricUsage
usage_metrics_token_detail: UsageMetricsTokenDetail
user_auth_info: UserAuthInfo
- user_tool_session_approval_commands: UserToolSessionApprovalCommands
- user_tool_session_approval_custom_tool: UserToolSessionApprovalCustomTool
- user_tool_session_approval_extension_management: UserToolSessionApprovalExtensionManagement
- user_tool_session_approval_extension_permission_access: UserToolSessionApprovalExtensionPermissionAccess
- user_tool_session_approval_mcp: UserToolSessionApprovalMCP
- user_tool_session_approval_memory: UserToolSessionApprovalMemory
- user_tool_session_approval_read: UserToolSessionApprovalRead
- user_tool_session_approval_write: UserToolSessionApprovalWrite
workspaces_checkpoints: WorkspacesCheckpoints
workspaces_create_file_request: WorkspacesCreateFileRequest
workspaces_get_workspace_result: WorkspacesGetWorkspaceResult
@@ -15490,7 +14204,7 @@ def from_dict(obj: Any) -> 'RPC':
agent_select_request = AgentSelectRequest.from_dict(obj.get("AgentSelectRequest"))
agent_select_result = AgentSelectResult.from_dict(obj.get("AgentSelectResult"))
api_key_auth_info = APIKeyAuthInfo.from_dict(obj.get("ApiKeyAuthInfo"))
- auth_info = AuthInfo.from_dict(obj.get("AuthInfo"))
+ auth_info = _load_AuthInfo(obj.get("AuthInfo"))
auth_info_type = AuthInfoType(obj.get("AuthInfoType"))
command_list = CommandList.from_dict(obj.get("CommandList"))
commands_handle_pending_command_request = CommandsHandlePendingCommandRequest.from_dict(obj.get("CommandsHandlePendingCommandRequest"))
@@ -15538,7 +14252,7 @@ def from_dict(obj: Any) -> 'RPC':
external_tool_text_result_for_llm = ExternalToolTextResultForLlm.from_dict(obj.get("ExternalToolTextResultForLlm"))
external_tool_text_result_for_llm_binary_results_for_llm = ExternalToolTextResultForLlmBinaryResultsForLlm.from_dict(obj.get("ExternalToolTextResultForLlmBinaryResultsForLlm"))
external_tool_text_result_for_llm_binary_results_for_llm_type = ExternalToolTextResultForLlmBinaryResultsForLlmType(obj.get("ExternalToolTextResultForLlmBinaryResultsForLlmType"))
- external_tool_text_result_for_llm_content = ExternalToolTextResultForLlmContent.from_dict(obj.get("ExternalToolTextResultForLlmContent"))
+ external_tool_text_result_for_llm_content = _load_ExternalToolTextResultForLlmContent(obj.get("ExternalToolTextResultForLlmContent"))
external_tool_text_result_for_llm_content_audio = ExternalToolTextResultForLlmContentAudio.from_dict(obj.get("ExternalToolTextResultForLlmContentAudio"))
external_tool_text_result_for_llm_content_image = ExternalToolTextResultForLlmContentImage.from_dict(obj.get("ExternalToolTextResultForLlmContentImage"))
external_tool_text_result_for_llm_content_resource = ExternalToolTextResultForLlmContentResource.from_dict(obj.get("ExternalToolTextResultForLlmContentResource"))
@@ -15651,12 +14365,12 @@ def from_dict(obj: Any) -> 'RPC':
options_update_env_value_mode = MCPSetEnvValueModeDetails(obj.get("OptionsUpdateEnvValueMode"))
pending_permission_request = PendingPermissionRequest.from_dict(obj.get("PendingPermissionRequest"))
pending_permission_request_list = PendingPermissionRequestList.from_dict(obj.get("PendingPermissionRequestList"))
- permission_decision = PermissionDecision.from_dict(obj.get("PermissionDecision"))
+ permission_decision = _load_PermissionDecision(obj.get("PermissionDecision"))
permission_decision_approved = PermissionDecisionApproved.from_dict(obj.get("PermissionDecisionApproved"))
permission_decision_approved_for_location = PermissionDecisionApprovedForLocation.from_dict(obj.get("PermissionDecisionApprovedForLocation"))
permission_decision_approved_for_session = PermissionDecisionApprovedForSession.from_dict(obj.get("PermissionDecisionApprovedForSession"))
permission_decision_approve_for_location = PermissionDecisionApproveForLocation.from_dict(obj.get("PermissionDecisionApproveForLocation"))
- permission_decision_approve_for_location_approval = PermissionDecisionApproveForLocationApproval.from_dict(obj.get("PermissionDecisionApproveForLocationApproval"))
+ permission_decision_approve_for_location_approval = _load_PermissionDecisionApproveForLocationApproval(obj.get("PermissionDecisionApproveForLocationApproval"))
permission_decision_approve_for_location_approval_commands = PermissionDecisionApproveForLocationApprovalCommands.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalCommands"))
permission_decision_approve_for_location_approval_custom_tool = PermissionDecisionApproveForLocationApprovalCustomTool.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalCustomTool"))
permission_decision_approve_for_location_approval_extension_management = PermissionDecisionApproveForLocationApprovalExtensionManagement.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalExtensionManagement"))
@@ -15667,7 +14381,7 @@ def from_dict(obj: Any) -> 'RPC':
permission_decision_approve_for_location_approval_read = PermissionDecisionApproveForLocationApprovalRead.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalRead"))
permission_decision_approve_for_location_approval_write = PermissionDecisionApproveForLocationApprovalWrite.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalWrite"))
permission_decision_approve_for_session = PermissionDecisionApproveForSession.from_dict(obj.get("PermissionDecisionApproveForSession"))
- permission_decision_approve_for_session_approval = PermissionDecisionApproveForSessionApproval.from_dict(obj.get("PermissionDecisionApproveForSessionApproval"))
+ permission_decision_approve_for_session_approval = _load_PermissionDecisionApproveForSessionApproval(obj.get("PermissionDecisionApproveForSessionApproval"))
permission_decision_approve_for_session_approval_commands = PermissionDecisionApproveForSessionApprovalCommands.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalCommands"))
permission_decision_approve_for_session_approval_custom_tool = PermissionDecisionApproveForSessionApprovalCustomTool.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalCustomTool"))
permission_decision_approve_for_session_approval_extension_management = PermissionDecisionApproveForSessionApprovalExtensionManagement.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalExtensionManagement"))
@@ -15712,7 +14426,7 @@ def from_dict(obj: Any) -> 'RPC':
permissions_configure_params = PermissionsConfigureParams.from_dict(obj.get("PermissionsConfigureParams"))
permissions_configure_result = PermissionsConfigureResult.from_dict(obj.get("PermissionsConfigureResult"))
permissions_folder_trust_add_trusted_result = PermissionsFolderTrustAddTrustedResult.from_dict(obj.get("PermissionsFolderTrustAddTrustedResult"))
- permissions_locations_add_tool_approval_details = PermissionsLocationsAddToolApprovalDetails.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetails"))
+ permissions_locations_add_tool_approval_details = _load_PermissionsLocationsAddToolApprovalDetails(obj.get("PermissionsLocationsAddToolApprovalDetails"))
permissions_locations_add_tool_approval_details_commands = PermissionsLocationsAddToolApprovalDetailsCommands.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsCommands"))
permissions_locations_add_tool_approval_details_custom_tool = PermissionsLocationsAddToolApprovalDetailsCustomTool.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsCustomTool"))
permissions_locations_add_tool_approval_details_extension_management = PermissionsLocationsAddToolApprovalDetailsExtensionManagement.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsExtensionManagement"))
@@ -15749,7 +14463,7 @@ def from_dict(obj: Any) -> 'RPC':
plugin_list = PluginList.from_dict(obj.get("PluginList"))
queued_command_handled = QueuedCommandHandled.from_dict(obj.get("QueuedCommandHandled"))
queued_command_not_handled = QueuedCommandNotHandled.from_dict(obj.get("QueuedCommandNotHandled"))
- queued_command_result = QueuedCommandResult.from_dict(obj.get("QueuedCommandResult"))
+ queued_command_result = _load_QueuedCommandResult(obj.get("QueuedCommandResult"))
queue_pending_items = QueuePendingItems.from_dict(obj.get("QueuePendingItems"))
queue_pending_items_kind = QueuePendingItemsKind(obj.get("QueuePendingItemsKind"))
queue_pending_items_result = QueuePendingItemsResult.from_dict(obj.get("QueuePendingItemsResult"))
@@ -15770,7 +14484,7 @@ def from_dict(obj: Any) -> 'RPC':
secrets_add_filter_values_request = SecretsAddFilterValuesRequest.from_dict(obj.get("SecretsAddFilterValuesRequest"))
secrets_add_filter_values_result = SecretsAddFilterValuesResult.from_dict(obj.get("SecretsAddFilterValuesResult"))
send_agent_mode = SendAgentMode(obj.get("SendAgentMode"))
- send_attachment = SendAttachment.from_dict(obj.get("SendAttachment"))
+ send_attachment = _load_SendAttachment(obj.get("SendAttachment"))
send_attachment_blob = SendAttachmentBlob.from_dict(obj.get("SendAttachmentBlob"))
send_attachment_directory = SendAttachmentDirectory.from_dict(obj.get("SendAttachmentDirectory"))
send_attachment_file = SendAttachmentFile.from_dict(obj.get("SendAttachmentFile"))
@@ -15888,7 +14602,7 @@ def from_dict(obj: Any) -> 'RPC':
slash_command_info = SlashCommandInfo.from_dict(obj.get("SlashCommandInfo"))
slash_command_input = SlashCommandInput.from_dict(obj.get("SlashCommandInput"))
slash_command_input_completion = SlashCommandInputCompletion(obj.get("SlashCommandInputCompletion"))
- slash_command_invocation_result = SlashCommandInvocationResult.from_dict(obj.get("SlashCommandInvocationResult"))
+ slash_command_invocation_result = _load_SlashCommandInvocationResult(obj.get("SlashCommandInvocationResult"))
slash_command_kind = SlashCommandKind(obj.get("SlashCommandKind"))
slash_command_select_subcommand_option = SlashCommandSelectSubcommandOption.from_dict(obj.get("SlashCommandSelectSubcommandOption"))
slash_command_select_subcommand_result = SlashCommandSelectSubcommandResult.from_dict(obj.get("SlashCommandSelectSubcommandResult"))
@@ -15896,7 +14610,7 @@ def from_dict(obj: Any) -> 'RPC':
task_agent_info = TaskAgentInfo.from_dict(obj.get("TaskAgentInfo"))
task_agent_progress = TaskAgentProgress.from_dict(obj.get("TaskAgentProgress"))
task_execution_mode = TaskExecutionMode(obj.get("TaskExecutionMode"))
- task_info = TaskInfo.from_dict(obj.get("TaskInfo"))
+ task_info = _load_TaskInfo(obj.get("TaskInfo"))
task_list = TaskList.from_dict(obj.get("TaskList"))
task_progress_line = TaskProgressLine.from_dict(obj.get("TaskProgressLine"))
tasks_cancel_request = TasksCancelRequest.from_dict(obj.get("TasksCancelRequest"))
@@ -15968,14 +14682,6 @@ def from_dict(obj: Any) -> 'RPC':
usage_metrics_model_metric_usage = UsageMetricsModelMetricUsage.from_dict(obj.get("UsageMetricsModelMetricUsage"))
usage_metrics_token_detail = UsageMetricsTokenDetail.from_dict(obj.get("UsageMetricsTokenDetail"))
user_auth_info = UserAuthInfo.from_dict(obj.get("UserAuthInfo"))
- user_tool_session_approval_commands = UserToolSessionApprovalCommands.from_dict(obj.get("UserToolSessionApprovalCommands"))
- user_tool_session_approval_custom_tool = UserToolSessionApprovalCustomTool.from_dict(obj.get("UserToolSessionApprovalCustomTool"))
- user_tool_session_approval_extension_management = UserToolSessionApprovalExtensionManagement.from_dict(obj.get("UserToolSessionApprovalExtensionManagement"))
- user_tool_session_approval_extension_permission_access = UserToolSessionApprovalExtensionPermissionAccess.from_dict(obj.get("UserToolSessionApprovalExtensionPermissionAccess"))
- user_tool_session_approval_mcp = UserToolSessionApprovalMCP.from_dict(obj.get("UserToolSessionApprovalMcp"))
- user_tool_session_approval_memory = UserToolSessionApprovalMemory.from_dict(obj.get("UserToolSessionApprovalMemory"))
- user_tool_session_approval_read = UserToolSessionApprovalRead.from_dict(obj.get("UserToolSessionApprovalRead"))
- user_tool_session_approval_write = UserToolSessionApprovalWrite.from_dict(obj.get("UserToolSessionApprovalWrite"))
workspaces_checkpoints = WorkspacesCheckpoints.from_dict(obj.get("WorkspacesCheckpoints"))
workspaces_create_file_request = WorkspacesCreateFileRequest.from_dict(obj.get("WorkspacesCreateFileRequest"))
workspaces_get_workspace_result = WorkspacesGetWorkspaceResult.from_dict(obj.get("WorkspacesGetWorkspaceResult"))
@@ -15992,7 +14698,7 @@ def from_dict(obj: Any) -> 'RPC':
session_context_info = from_union([SessionContextInfo.from_dict, from_none], obj.get("SessionContextInfo"))
task_progress = from_union([TaskProgress.from_dict, from_none], obj.get("TaskProgress"))
workspace_summary = from_union([WorkspaceSummary.from_dict, from_none], obj.get("WorkspaceSummary"))
- return RPC(abort_request, abort_result, account_get_quota_request, account_get_quota_result, account_quota_snapshot, agent_get_current_result, agent_info, agent_info_source, agent_list, agent_reload_result, agent_select_request, agent_select_result, api_key_auth_info, auth_info, auth_info_type, command_list, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_invoke_request, commands_list_request, commands_respond_to_queued_command_request, commands_respond_to_queued_command_result, connected_remote_session_metadata, connected_remote_session_metadata_kind, connected_remote_session_metadata_repository, connect_remote_session_params, connect_request, connect_result, content_filter_mode, copilot_api_token_auth_info, copilot_user_response, copilot_user_response_endpoints, copilot_user_response_quota_snapshots, copilot_user_response_quota_snapshots_chat, copilot_user_response_quota_snapshots_completions, copilot_user_response_quota_snapshots_premium_interactions, current_model, discovered_mcp_server, discovered_mcp_server_type, enqueue_command_params, enqueue_command_result, env_auth_info, event_log_read_request, event_log_release_interest_result, event_log_tail_result, event_log_types, events_agent_scope, events_cursor_status, events_read_result, execute_command_params, execute_command_result, extension, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, external_tool_result, external_tool_text_result_for_llm, external_tool_text_result_for_llm_binary_results_for_llm, external_tool_text_result_for_llm_binary_results_for_llm_type, external_tool_text_result_for_llm_content, external_tool_text_result_for_llm_content_audio, external_tool_text_result_for_llm_content_image, external_tool_text_result_for_llm_content_resource, external_tool_text_result_for_llm_content_resource_details, external_tool_text_result_for_llm_content_resource_link, external_tool_text_result_for_llm_content_resource_link_icon, external_tool_text_result_for_llm_content_resource_link_icon_theme, external_tool_text_result_for_llm_content_terminal, external_tool_text_result_for_llm_content_text, filter_mapping, fleet_start_request, fleet_start_result, folder_trust_add_params, folder_trust_check_params, folder_trust_check_result, gh_cli_auth_info, handle_pending_tool_call_request, handle_pending_tool_call_result, history_abort_manual_compaction_result, history_cancel_background_compaction_result, history_compact_context_window, history_compact_request, history_compact_result, history_summarize_for_handoff_result, history_truncate_request, history_truncate_result, hmac_auth_info, installed_plugin, installed_plugin_source, installed_plugin_source_github, installed_plugin_source_local, installed_plugin_source_url, instructions_get_sources_result, instructions_sources, instructions_sources_location, instructions_sources_type, log_request, log_result, lsp_initialize_request, mcp_cancel_sampling_execution_params, mcp_cancel_sampling_execution_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_enable_request, mcp_execute_sampling_params, mcp_execute_sampling_request, mcp_execute_sampling_result, mcp_oauth_login_request, mcp_oauth_login_result, mcp_remove_git_hub_result, mcp_sampling_execution_action, mcp_sampling_execution_result, mcp_server, mcp_server_config, mcp_server_config_http, mcp_server_config_http_auth, mcp_server_config_http_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_stdio, mcp_server_list, mcp_set_env_value_mode_details, mcp_set_env_value_mode_params, mcp_set_env_value_mode_result, metadata_context_info_request, metadata_context_info_result, metadata_is_processing_result, metadata_recompute_context_tokens_request, metadata_recompute_context_tokens_result, metadata_record_context_change_request, metadata_record_context_change_result, metadata_set_working_directory_request, metadata_set_working_directory_result, metadata_snapshot_current_mode, metadata_snapshot_remote_metadata, metadata_snapshot_remote_metadata_repository, metadata_snapshot_remote_metadata_task_type, model, model_billing, model_billing_token_prices, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_picker_category, model_picker_price_category, model_policy, model_policy_state, model_set_reasoning_effort_request, model_set_reasoning_effort_result, models_list_request, model_switch_to_request, model_switch_to_result, mode_set_request, name_get_result, name_set_auto_request, name_set_auto_result, name_set_request, options_update_env_value_mode, pending_permission_request, pending_permission_request_list, permission_decision, permission_decision_approved, permission_decision_approved_for_location, permission_decision_approved_for_session, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_extension_management, permission_decision_approve_for_location_approval_extension_permission_access, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_extension_management, permission_decision_approve_for_session_approval_extension_permission_access, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_cancelled, permission_decision_denied_by_content_exclusion_policy, permission_decision_denied_by_permission_request_hook, permission_decision_denied_by_rules, permission_decision_denied_interactively_by_user, permission_decision_denied_no_approval_rule_and_could_not_request_from_user, permission_decision_reject, permission_decision_request, permission_decision_user_not_available, permission_location_add_tool_approval_params, permission_location_apply_params, permission_location_apply_result, permission_location_resolve_params, permission_location_resolve_result, permission_location_type, permission_paths_add_params, permission_paths_allowed_check_params, permission_paths_allowed_check_result, permission_paths_config, permission_paths_list, permission_paths_update_primary_params, permission_paths_workspace_check_params, permission_paths_workspace_check_result, permission_prompt_shown_notification, permission_request_result, permission_rules_set, permissions_configure_additional_content_exclusion_policy, permissions_configure_additional_content_exclusion_policy_rule, permissions_configure_additional_content_exclusion_policy_rule_source, permissions_configure_additional_content_exclusion_policy_scope, permissions_configure_params, permissions_configure_result, permissions_folder_trust_add_trusted_result, permissions_locations_add_tool_approval_details, permissions_locations_add_tool_approval_details_commands, permissions_locations_add_tool_approval_details_custom_tool, permissions_locations_add_tool_approval_details_extension_management, permissions_locations_add_tool_approval_details_extension_permission_access, permissions_locations_add_tool_approval_details_mcp, permissions_locations_add_tool_approval_details_mcp_sampling, permissions_locations_add_tool_approval_details_memory, permissions_locations_add_tool_approval_details_read, permissions_locations_add_tool_approval_details_write, permissions_locations_add_tool_approval_result, permissions_modify_rules_params, permissions_modify_rules_result, permissions_modify_rules_scope, permissions_notify_prompt_shown_result, permissions_paths_add_result, permissions_paths_list_request, permissions_paths_update_primary_result, permissions_pending_requests_request, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, permissions_set_approve_all_source, permissions_set_required_request, permissions_set_required_result, permissions_urls_set_unrestricted_mode_result, permission_urls_config, permission_urls_set_unrestricted_mode_params, ping_request, ping_result, plan_read_result, plan_update_request, plugin, plugin_list, queued_command_handled, queued_command_not_handled, queued_command_result, queue_pending_items, queue_pending_items_kind, queue_pending_items_result, queue_remove_most_recent_result, register_event_interest_params, register_event_interest_result, release_event_interest_params, remote_enable_request, remote_enable_result, remote_notify_steerable_changed_request, remote_notify_steerable_changed_result, remote_session_connection_result, remote_session_mode, schedule_entry, schedule_list, schedule_stop_request, schedule_stop_result, secrets_add_filter_values_request, secrets_add_filter_values_result, send_agent_mode, send_attachment, send_attachment_blob, send_attachment_directory, send_attachment_file, send_attachment_file_line_range, send_attachment_github_reference, send_attachment_github_reference_type, send_attachment_selection, send_attachment_selection_details, send_attachment_selection_details_end, send_attachment_selection_details_start, send_mode, send_request, send_result, server_skill, server_skill_list, session_auth_status, session_bulk_delete_result, session_context, session_context_host_type, session_enrich_metadata_result, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_capabilities, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_sqlite_exists_request, session_fs_sqlite_exists_result, session_fs_sqlite_query_request, session_fs_sqlite_query_result, session_fs_sqlite_query_type, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_installed_plugin, session_installed_plugin_source, session_installed_plugin_source_github, session_installed_plugin_source_local, session_installed_plugin_source_url, session_list, session_list_filter, session_load_deferred_repo_hooks_result, session_log_level, session_metadata, session_metadata_snapshot, session_mode, session_prune_result, sessions_bulk_delete_request, sessions_check_in_use_request, sessions_check_in_use_result, sessions_close_request, sessions_close_result, sessions_enrich_metadata_request, session_set_credentials_params, session_set_credentials_result, sessions_find_by_prefix_request, sessions_find_by_prefix_result, sessions_find_by_task_id_request, sessions_find_by_task_id_result, sessions_fork_request, sessions_fork_result, sessions_get_event_file_path_request, sessions_get_event_file_path_result, sessions_get_last_for_context_request, sessions_get_last_for_context_result, sessions_get_persisted_remote_steerable_request, sessions_get_persisted_remote_steerable_result, session_sizes, sessions_list_request, sessions_load_deferred_repo_hooks_request, sessions_prune_old_request, sessions_release_lock_request, sessions_release_lock_result, sessions_reload_plugin_hooks_request, sessions_reload_plugin_hooks_result, sessions_save_request, sessions_save_result, sessions_set_additional_plugins_request, sessions_set_additional_plugins_result, session_update_options_params, session_update_options_result, session_working_directory_context, session_working_directory_context_host_type, shell_exec_request, shell_exec_result, shell_kill_request, shell_kill_result, shell_kill_signal, shutdown_request, skill, skill_list, skills_config_set_disabled_skills_request, skills_disable_request, skills_discover_request, skills_enable_request, skills_get_invoked_result, skills_invoked_skill, skills_load_diagnostics, slash_command_agent_prompt_result, slash_command_completed_result, slash_command_info, slash_command_input, slash_command_input_completion, slash_command_invocation_result, slash_command_kind, slash_command_select_subcommand_option, slash_command_select_subcommand_result, slash_command_text_result, task_agent_info, task_agent_progress, task_execution_mode, task_info, task_list, task_progress_line, tasks_cancel_request, tasks_cancel_result, tasks_get_current_promotable_result, tasks_get_progress_request, tasks_get_progress_result, task_shell_info, task_shell_info_attachment_mode, task_shell_progress, tasks_promote_current_to_background_result, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_refresh_result, tasks_remove_request, tasks_remove_result, tasks_send_message_request, tasks_send_message_result, tasks_start_agent_request, tasks_start_agent_result, task_status, tasks_wait_for_pending_result, telemetry_set_feature_overrides_request, token_auth_info, tool, tool_list, tools_initialize_and_validate_result, tools_list_request, ui_auto_mode_switch_response, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_exit_plan_mode_action, ui_exit_plan_mode_response, ui_handle_pending_auto_mode_switch_request, ui_handle_pending_elicitation_request, ui_handle_pending_exit_plan_mode_request, ui_handle_pending_result, ui_handle_pending_sampling_request, ui_handle_pending_sampling_response, ui_handle_pending_user_input_request, ui_register_direct_auto_mode_switch_handler_result, ui_unregister_direct_auto_mode_switch_handler_request, ui_unregister_direct_auto_mode_switch_handler_result, ui_user_input_response, usage_get_metrics_result, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, user_auth_info, user_tool_session_approval_commands, user_tool_session_approval_custom_tool, user_tool_session_approval_extension_management, user_tool_session_approval_extension_permission_access, user_tool_session_approval_mcp, user_tool_session_approval_memory, user_tool_session_approval_read, user_tool_session_approval_write, workspaces_checkpoints, workspaces_create_file_request, workspaces_get_workspace_result, workspaces_list_checkpoints_result, workspaces_list_files_result, workspaces_read_checkpoint_request, workspaces_read_checkpoint_result, workspaces_read_file_request, workspaces_read_file_result, workspaces_save_large_paste_request, workspaces_save_large_paste_result, workspace_summary_host_type, workspaces_workspace_details_host_type, session_context_info, task_progress, workspace_summary)
+ return RPC(abort_request, abort_result, account_get_quota_request, account_get_quota_result, account_quota_snapshot, agent_get_current_result, agent_info, agent_info_source, agent_list, agent_reload_result, agent_select_request, agent_select_result, api_key_auth_info, auth_info, auth_info_type, command_list, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_invoke_request, commands_list_request, commands_respond_to_queued_command_request, commands_respond_to_queued_command_result, connected_remote_session_metadata, connected_remote_session_metadata_kind, connected_remote_session_metadata_repository, connect_remote_session_params, connect_request, connect_result, content_filter_mode, copilot_api_token_auth_info, copilot_user_response, copilot_user_response_endpoints, copilot_user_response_quota_snapshots, copilot_user_response_quota_snapshots_chat, copilot_user_response_quota_snapshots_completions, copilot_user_response_quota_snapshots_premium_interactions, current_model, discovered_mcp_server, discovered_mcp_server_type, enqueue_command_params, enqueue_command_result, env_auth_info, event_log_read_request, event_log_release_interest_result, event_log_tail_result, event_log_types, events_agent_scope, events_cursor_status, events_read_result, execute_command_params, execute_command_result, extension, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, external_tool_result, external_tool_text_result_for_llm, external_tool_text_result_for_llm_binary_results_for_llm, external_tool_text_result_for_llm_binary_results_for_llm_type, external_tool_text_result_for_llm_content, external_tool_text_result_for_llm_content_audio, external_tool_text_result_for_llm_content_image, external_tool_text_result_for_llm_content_resource, external_tool_text_result_for_llm_content_resource_details, external_tool_text_result_for_llm_content_resource_link, external_tool_text_result_for_llm_content_resource_link_icon, external_tool_text_result_for_llm_content_resource_link_icon_theme, external_tool_text_result_for_llm_content_terminal, external_tool_text_result_for_llm_content_text, filter_mapping, fleet_start_request, fleet_start_result, folder_trust_add_params, folder_trust_check_params, folder_trust_check_result, gh_cli_auth_info, handle_pending_tool_call_request, handle_pending_tool_call_result, history_abort_manual_compaction_result, history_cancel_background_compaction_result, history_compact_context_window, history_compact_request, history_compact_result, history_summarize_for_handoff_result, history_truncate_request, history_truncate_result, hmac_auth_info, installed_plugin, installed_plugin_source, installed_plugin_source_github, installed_plugin_source_local, installed_plugin_source_url, instructions_get_sources_result, instructions_sources, instructions_sources_location, instructions_sources_type, log_request, log_result, lsp_initialize_request, mcp_cancel_sampling_execution_params, mcp_cancel_sampling_execution_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_enable_request, mcp_execute_sampling_params, mcp_execute_sampling_request, mcp_execute_sampling_result, mcp_oauth_login_request, mcp_oauth_login_result, mcp_remove_git_hub_result, mcp_sampling_execution_action, mcp_sampling_execution_result, mcp_server, mcp_server_config, mcp_server_config_http, mcp_server_config_http_auth, mcp_server_config_http_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_stdio, mcp_server_list, mcp_set_env_value_mode_details, mcp_set_env_value_mode_params, mcp_set_env_value_mode_result, metadata_context_info_request, metadata_context_info_result, metadata_is_processing_result, metadata_recompute_context_tokens_request, metadata_recompute_context_tokens_result, metadata_record_context_change_request, metadata_record_context_change_result, metadata_set_working_directory_request, metadata_set_working_directory_result, metadata_snapshot_current_mode, metadata_snapshot_remote_metadata, metadata_snapshot_remote_metadata_repository, metadata_snapshot_remote_metadata_task_type, model, model_billing, model_billing_token_prices, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_picker_category, model_picker_price_category, model_policy, model_policy_state, model_set_reasoning_effort_request, model_set_reasoning_effort_result, models_list_request, model_switch_to_request, model_switch_to_result, mode_set_request, name_get_result, name_set_auto_request, name_set_auto_result, name_set_request, options_update_env_value_mode, pending_permission_request, pending_permission_request_list, permission_decision, permission_decision_approved, permission_decision_approved_for_location, permission_decision_approved_for_session, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_extension_management, permission_decision_approve_for_location_approval_extension_permission_access, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_extension_management, permission_decision_approve_for_session_approval_extension_permission_access, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_cancelled, permission_decision_denied_by_content_exclusion_policy, permission_decision_denied_by_permission_request_hook, permission_decision_denied_by_rules, permission_decision_denied_interactively_by_user, permission_decision_denied_no_approval_rule_and_could_not_request_from_user, permission_decision_reject, permission_decision_request, permission_decision_user_not_available, permission_location_add_tool_approval_params, permission_location_apply_params, permission_location_apply_result, permission_location_resolve_params, permission_location_resolve_result, permission_location_type, permission_paths_add_params, permission_paths_allowed_check_params, permission_paths_allowed_check_result, permission_paths_config, permission_paths_list, permission_paths_update_primary_params, permission_paths_workspace_check_params, permission_paths_workspace_check_result, permission_prompt_shown_notification, permission_request_result, permission_rules_set, permissions_configure_additional_content_exclusion_policy, permissions_configure_additional_content_exclusion_policy_rule, permissions_configure_additional_content_exclusion_policy_rule_source, permissions_configure_additional_content_exclusion_policy_scope, permissions_configure_params, permissions_configure_result, permissions_folder_trust_add_trusted_result, permissions_locations_add_tool_approval_details, permissions_locations_add_tool_approval_details_commands, permissions_locations_add_tool_approval_details_custom_tool, permissions_locations_add_tool_approval_details_extension_management, permissions_locations_add_tool_approval_details_extension_permission_access, permissions_locations_add_tool_approval_details_mcp, permissions_locations_add_tool_approval_details_mcp_sampling, permissions_locations_add_tool_approval_details_memory, permissions_locations_add_tool_approval_details_read, permissions_locations_add_tool_approval_details_write, permissions_locations_add_tool_approval_result, permissions_modify_rules_params, permissions_modify_rules_result, permissions_modify_rules_scope, permissions_notify_prompt_shown_result, permissions_paths_add_result, permissions_paths_list_request, permissions_paths_update_primary_result, permissions_pending_requests_request, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, permissions_set_approve_all_source, permissions_set_required_request, permissions_set_required_result, permissions_urls_set_unrestricted_mode_result, permission_urls_config, permission_urls_set_unrestricted_mode_params, ping_request, ping_result, plan_read_result, plan_update_request, plugin, plugin_list, queued_command_handled, queued_command_not_handled, queued_command_result, queue_pending_items, queue_pending_items_kind, queue_pending_items_result, queue_remove_most_recent_result, register_event_interest_params, register_event_interest_result, release_event_interest_params, remote_enable_request, remote_enable_result, remote_notify_steerable_changed_request, remote_notify_steerable_changed_result, remote_session_connection_result, remote_session_mode, schedule_entry, schedule_list, schedule_stop_request, schedule_stop_result, secrets_add_filter_values_request, secrets_add_filter_values_result, send_agent_mode, send_attachment, send_attachment_blob, send_attachment_directory, send_attachment_file, send_attachment_file_line_range, send_attachment_github_reference, send_attachment_github_reference_type, send_attachment_selection, send_attachment_selection_details, send_attachment_selection_details_end, send_attachment_selection_details_start, send_mode, send_request, send_result, server_skill, server_skill_list, session_auth_status, session_bulk_delete_result, session_context, session_context_host_type, session_enrich_metadata_result, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_capabilities, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_sqlite_exists_request, session_fs_sqlite_exists_result, session_fs_sqlite_query_request, session_fs_sqlite_query_result, session_fs_sqlite_query_type, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_installed_plugin, session_installed_plugin_source, session_installed_plugin_source_github, session_installed_plugin_source_local, session_installed_plugin_source_url, session_list, session_list_filter, session_load_deferred_repo_hooks_result, session_log_level, session_metadata, session_metadata_snapshot, session_mode, session_prune_result, sessions_bulk_delete_request, sessions_check_in_use_request, sessions_check_in_use_result, sessions_close_request, sessions_close_result, sessions_enrich_metadata_request, session_set_credentials_params, session_set_credentials_result, sessions_find_by_prefix_request, sessions_find_by_prefix_result, sessions_find_by_task_id_request, sessions_find_by_task_id_result, sessions_fork_request, sessions_fork_result, sessions_get_event_file_path_request, sessions_get_event_file_path_result, sessions_get_last_for_context_request, sessions_get_last_for_context_result, sessions_get_persisted_remote_steerable_request, sessions_get_persisted_remote_steerable_result, session_sizes, sessions_list_request, sessions_load_deferred_repo_hooks_request, sessions_prune_old_request, sessions_release_lock_request, sessions_release_lock_result, sessions_reload_plugin_hooks_request, sessions_reload_plugin_hooks_result, sessions_save_request, sessions_save_result, sessions_set_additional_plugins_request, sessions_set_additional_plugins_result, session_update_options_params, session_update_options_result, session_working_directory_context, session_working_directory_context_host_type, shell_exec_request, shell_exec_result, shell_kill_request, shell_kill_result, shell_kill_signal, shutdown_request, skill, skill_list, skills_config_set_disabled_skills_request, skills_disable_request, skills_discover_request, skills_enable_request, skills_get_invoked_result, skills_invoked_skill, skills_load_diagnostics, slash_command_agent_prompt_result, slash_command_completed_result, slash_command_info, slash_command_input, slash_command_input_completion, slash_command_invocation_result, slash_command_kind, slash_command_select_subcommand_option, slash_command_select_subcommand_result, slash_command_text_result, task_agent_info, task_agent_progress, task_execution_mode, task_info, task_list, task_progress_line, tasks_cancel_request, tasks_cancel_result, tasks_get_current_promotable_result, tasks_get_progress_request, tasks_get_progress_result, task_shell_info, task_shell_info_attachment_mode, task_shell_progress, tasks_promote_current_to_background_result, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_refresh_result, tasks_remove_request, tasks_remove_result, tasks_send_message_request, tasks_send_message_result, tasks_start_agent_request, tasks_start_agent_result, task_status, tasks_wait_for_pending_result, telemetry_set_feature_overrides_request, token_auth_info, tool, tool_list, tools_initialize_and_validate_result, tools_list_request, ui_auto_mode_switch_response, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_exit_plan_mode_action, ui_exit_plan_mode_response, ui_handle_pending_auto_mode_switch_request, ui_handle_pending_elicitation_request, ui_handle_pending_exit_plan_mode_request, ui_handle_pending_result, ui_handle_pending_sampling_request, ui_handle_pending_sampling_response, ui_handle_pending_user_input_request, ui_register_direct_auto_mode_switch_handler_result, ui_unregister_direct_auto_mode_switch_handler_request, ui_unregister_direct_auto_mode_switch_handler_result, ui_user_input_response, usage_get_metrics_result, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, user_auth_info, workspaces_checkpoints, workspaces_create_file_request, workspaces_get_workspace_result, workspaces_list_checkpoints_result, workspaces_list_files_result, workspaces_read_checkpoint_request, workspaces_read_checkpoint_result, workspaces_read_file_request, workspaces_read_file_result, workspaces_save_large_paste_request, workspaces_save_large_paste_result, workspace_summary_host_type, workspaces_workspace_details_host_type, session_context_info, task_progress, workspace_summary)
def to_dict(self) -> dict:
result: dict = {}
@@ -16009,7 +14715,7 @@ def to_dict(self) -> dict:
result["AgentSelectRequest"] = to_class(AgentSelectRequest, self.agent_select_request)
result["AgentSelectResult"] = to_class(AgentSelectResult, self.agent_select_result)
result["ApiKeyAuthInfo"] = to_class(APIKeyAuthInfo, self.api_key_auth_info)
- result["AuthInfo"] = to_class(AuthInfo, self.auth_info)
+ result["AuthInfo"] = (self.auth_info).to_dict()
result["AuthInfoType"] = to_enum(AuthInfoType, self.auth_info_type)
result["CommandList"] = to_class(CommandList, self.command_list)
result["CommandsHandlePendingCommandRequest"] = to_class(CommandsHandlePendingCommandRequest, self.commands_handle_pending_command_request)
@@ -16057,7 +14763,7 @@ def to_dict(self) -> dict:
result["ExternalToolTextResultForLlm"] = to_class(ExternalToolTextResultForLlm, self.external_tool_text_result_for_llm)
result["ExternalToolTextResultForLlmBinaryResultsForLlm"] = to_class(ExternalToolTextResultForLlmBinaryResultsForLlm, self.external_tool_text_result_for_llm_binary_results_for_llm)
result["ExternalToolTextResultForLlmBinaryResultsForLlmType"] = to_enum(ExternalToolTextResultForLlmBinaryResultsForLlmType, self.external_tool_text_result_for_llm_binary_results_for_llm_type)
- result["ExternalToolTextResultForLlmContent"] = to_class(ExternalToolTextResultForLlmContent, self.external_tool_text_result_for_llm_content)
+ result["ExternalToolTextResultForLlmContent"] = (self.external_tool_text_result_for_llm_content).to_dict()
result["ExternalToolTextResultForLlmContentAudio"] = to_class(ExternalToolTextResultForLlmContentAudio, self.external_tool_text_result_for_llm_content_audio)
result["ExternalToolTextResultForLlmContentImage"] = to_class(ExternalToolTextResultForLlmContentImage, self.external_tool_text_result_for_llm_content_image)
result["ExternalToolTextResultForLlmContentResource"] = to_class(ExternalToolTextResultForLlmContentResource, self.external_tool_text_result_for_llm_content_resource)
@@ -16170,12 +14876,12 @@ def to_dict(self) -> dict:
result["OptionsUpdateEnvValueMode"] = to_enum(MCPSetEnvValueModeDetails, self.options_update_env_value_mode)
result["PendingPermissionRequest"] = to_class(PendingPermissionRequest, self.pending_permission_request)
result["PendingPermissionRequestList"] = to_class(PendingPermissionRequestList, self.pending_permission_request_list)
- result["PermissionDecision"] = to_class(PermissionDecision, self.permission_decision)
+ result["PermissionDecision"] = (self.permission_decision).to_dict()
result["PermissionDecisionApproved"] = to_class(PermissionDecisionApproved, self.permission_decision_approved)
result["PermissionDecisionApprovedForLocation"] = to_class(PermissionDecisionApprovedForLocation, self.permission_decision_approved_for_location)
result["PermissionDecisionApprovedForSession"] = to_class(PermissionDecisionApprovedForSession, self.permission_decision_approved_for_session)
result["PermissionDecisionApproveForLocation"] = to_class(PermissionDecisionApproveForLocation, self.permission_decision_approve_for_location)
- result["PermissionDecisionApproveForLocationApproval"] = to_class(PermissionDecisionApproveForLocationApproval, self.permission_decision_approve_for_location_approval)
+ result["PermissionDecisionApproveForLocationApproval"] = (self.permission_decision_approve_for_location_approval).to_dict()
result["PermissionDecisionApproveForLocationApprovalCommands"] = to_class(PermissionDecisionApproveForLocationApprovalCommands, self.permission_decision_approve_for_location_approval_commands)
result["PermissionDecisionApproveForLocationApprovalCustomTool"] = to_class(PermissionDecisionApproveForLocationApprovalCustomTool, self.permission_decision_approve_for_location_approval_custom_tool)
result["PermissionDecisionApproveForLocationApprovalExtensionManagement"] = to_class(PermissionDecisionApproveForLocationApprovalExtensionManagement, self.permission_decision_approve_for_location_approval_extension_management)
@@ -16186,7 +14892,7 @@ def to_dict(self) -> dict:
result["PermissionDecisionApproveForLocationApprovalRead"] = to_class(PermissionDecisionApproveForLocationApprovalRead, self.permission_decision_approve_for_location_approval_read)
result["PermissionDecisionApproveForLocationApprovalWrite"] = to_class(PermissionDecisionApproveForLocationApprovalWrite, self.permission_decision_approve_for_location_approval_write)
result["PermissionDecisionApproveForSession"] = to_class(PermissionDecisionApproveForSession, self.permission_decision_approve_for_session)
- result["PermissionDecisionApproveForSessionApproval"] = to_class(PermissionDecisionApproveForSessionApproval, self.permission_decision_approve_for_session_approval)
+ result["PermissionDecisionApproveForSessionApproval"] = (self.permission_decision_approve_for_session_approval).to_dict()
result["PermissionDecisionApproveForSessionApprovalCommands"] = to_class(PermissionDecisionApproveForSessionApprovalCommands, self.permission_decision_approve_for_session_approval_commands)
result["PermissionDecisionApproveForSessionApprovalCustomTool"] = to_class(PermissionDecisionApproveForSessionApprovalCustomTool, self.permission_decision_approve_for_session_approval_custom_tool)
result["PermissionDecisionApproveForSessionApprovalExtensionManagement"] = to_class(PermissionDecisionApproveForSessionApprovalExtensionManagement, self.permission_decision_approve_for_session_approval_extension_management)
@@ -16231,7 +14937,7 @@ def to_dict(self) -> dict:
result["PermissionsConfigureParams"] = to_class(PermissionsConfigureParams, self.permissions_configure_params)
result["PermissionsConfigureResult"] = to_class(PermissionsConfigureResult, self.permissions_configure_result)
result["PermissionsFolderTrustAddTrustedResult"] = to_class(PermissionsFolderTrustAddTrustedResult, self.permissions_folder_trust_add_trusted_result)
- result["PermissionsLocationsAddToolApprovalDetails"] = to_class(PermissionsLocationsAddToolApprovalDetails, self.permissions_locations_add_tool_approval_details)
+ result["PermissionsLocationsAddToolApprovalDetails"] = (self.permissions_locations_add_tool_approval_details).to_dict()
result["PermissionsLocationsAddToolApprovalDetailsCommands"] = to_class(PermissionsLocationsAddToolApprovalDetailsCommands, self.permissions_locations_add_tool_approval_details_commands)
result["PermissionsLocationsAddToolApprovalDetailsCustomTool"] = to_class(PermissionsLocationsAddToolApprovalDetailsCustomTool, self.permissions_locations_add_tool_approval_details_custom_tool)
result["PermissionsLocationsAddToolApprovalDetailsExtensionManagement"] = to_class(PermissionsLocationsAddToolApprovalDetailsExtensionManagement, self.permissions_locations_add_tool_approval_details_extension_management)
@@ -16268,7 +14974,7 @@ def to_dict(self) -> dict:
result["PluginList"] = to_class(PluginList, self.plugin_list)
result["QueuedCommandHandled"] = to_class(QueuedCommandHandled, self.queued_command_handled)
result["QueuedCommandNotHandled"] = to_class(QueuedCommandNotHandled, self.queued_command_not_handled)
- result["QueuedCommandResult"] = to_class(QueuedCommandResult, self.queued_command_result)
+ result["QueuedCommandResult"] = (self.queued_command_result).to_dict()
result["QueuePendingItems"] = to_class(QueuePendingItems, self.queue_pending_items)
result["QueuePendingItemsKind"] = to_enum(QueuePendingItemsKind, self.queue_pending_items_kind)
result["QueuePendingItemsResult"] = to_class(QueuePendingItemsResult, self.queue_pending_items_result)
@@ -16289,7 +14995,7 @@ def to_dict(self) -> dict:
result["SecretsAddFilterValuesRequest"] = to_class(SecretsAddFilterValuesRequest, self.secrets_add_filter_values_request)
result["SecretsAddFilterValuesResult"] = to_class(SecretsAddFilterValuesResult, self.secrets_add_filter_values_result)
result["SendAgentMode"] = to_enum(SendAgentMode, self.send_agent_mode)
- result["SendAttachment"] = to_class(SendAttachment, self.send_attachment)
+ result["SendAttachment"] = (self.send_attachment).to_dict()
result["SendAttachmentBlob"] = to_class(SendAttachmentBlob, self.send_attachment_blob)
result["SendAttachmentDirectory"] = to_class(SendAttachmentDirectory, self.send_attachment_directory)
result["SendAttachmentFile"] = to_class(SendAttachmentFile, self.send_attachment_file)
@@ -16407,7 +15113,7 @@ def to_dict(self) -> dict:
result["SlashCommandInfo"] = to_class(SlashCommandInfo, self.slash_command_info)
result["SlashCommandInput"] = to_class(SlashCommandInput, self.slash_command_input)
result["SlashCommandInputCompletion"] = to_enum(SlashCommandInputCompletion, self.slash_command_input_completion)
- result["SlashCommandInvocationResult"] = to_class(SlashCommandInvocationResult, self.slash_command_invocation_result)
+ result["SlashCommandInvocationResult"] = (self.slash_command_invocation_result).to_dict()
result["SlashCommandKind"] = to_enum(SlashCommandKind, self.slash_command_kind)
result["SlashCommandSelectSubcommandOption"] = to_class(SlashCommandSelectSubcommandOption, self.slash_command_select_subcommand_option)
result["SlashCommandSelectSubcommandResult"] = to_class(SlashCommandSelectSubcommandResult, self.slash_command_select_subcommand_result)
@@ -16415,7 +15121,7 @@ def to_dict(self) -> dict:
result["TaskAgentInfo"] = to_class(TaskAgentInfo, self.task_agent_info)
result["TaskAgentProgress"] = to_class(TaskAgentProgress, self.task_agent_progress)
result["TaskExecutionMode"] = to_enum(TaskExecutionMode, self.task_execution_mode)
- result["TaskInfo"] = to_class(TaskInfo, self.task_info)
+ result["TaskInfo"] = (self.task_info).to_dict()
result["TaskList"] = to_class(TaskList, self.task_list)
result["TaskProgressLine"] = to_class(TaskProgressLine, self.task_progress_line)
result["TasksCancelRequest"] = to_class(TasksCancelRequest, self.tasks_cancel_request)
@@ -16487,14 +15193,6 @@ def to_dict(self) -> dict:
result["UsageMetricsModelMetricUsage"] = to_class(UsageMetricsModelMetricUsage, self.usage_metrics_model_metric_usage)
result["UsageMetricsTokenDetail"] = to_class(UsageMetricsTokenDetail, self.usage_metrics_token_detail)
result["UserAuthInfo"] = to_class(UserAuthInfo, self.user_auth_info)
- result["UserToolSessionApprovalCommands"] = to_class(UserToolSessionApprovalCommands, self.user_tool_session_approval_commands)
- result["UserToolSessionApprovalCustomTool"] = to_class(UserToolSessionApprovalCustomTool, self.user_tool_session_approval_custom_tool)
- result["UserToolSessionApprovalExtensionManagement"] = to_class(UserToolSessionApprovalExtensionManagement, self.user_tool_session_approval_extension_management)
- result["UserToolSessionApprovalExtensionPermissionAccess"] = to_class(UserToolSessionApprovalExtensionPermissionAccess, self.user_tool_session_approval_extension_permission_access)
- result["UserToolSessionApprovalMcp"] = to_class(UserToolSessionApprovalMCP, self.user_tool_session_approval_mcp)
- result["UserToolSessionApprovalMemory"] = to_class(UserToolSessionApprovalMemory, self.user_tool_session_approval_memory)
- result["UserToolSessionApprovalRead"] = to_class(UserToolSessionApprovalRead, self.user_tool_session_approval_read)
- result["UserToolSessionApprovalWrite"] = to_class(UserToolSessionApprovalWrite, self.user_tool_session_approval_write)
result["WorkspacesCheckpoints"] = to_class(WorkspacesCheckpoints, self.workspaces_checkpoints)
result["WorkspacesCreateFileRequest"] = to_class(WorkspacesCreateFileRequest, self.workspaces_create_file_request)
result["WorkspacesGetWorkspaceResult"] = to_class(WorkspacesGetWorkspaceResult, self.workspaces_get_workspace_result)
@@ -16519,6 +15217,164 @@ def rpc_from_dict(s: Any) -> RPC:
def rpc_to_dict(x: RPC) -> Any:
return to_class(RPC, x)
+# The new auth credentials to install on the session. When omitted or `undefined`, the call is a no-op and the session's existing credentials are preserved. The runtime stores the value verbatim and uses it for outbound model/API requests; it does NOT re-validate or re-fetch the associated Copilot user response. Several variants carry secret material; treat this method's params as containing secrets at rest and in transit.
+AuthInfo = HMACAuthInfo | EnvAuthInfo | TokenAuthInfo | CopilotAPITokenAuthInfo | UserAuthInfo | GhCLIAuthInfo | APIKeyAuthInfo
+
+def _load_AuthInfo(obj: Any) -> "AuthInfo":
+ assert isinstance(obj, dict)
+ kind = obj.get("type")
+ match kind:
+ case "hmac": return HMACAuthInfo.from_dict(obj)
+ case "env": return EnvAuthInfo.from_dict(obj)
+ case "token": return TokenAuthInfo.from_dict(obj)
+ case "copilot-api-token": return CopilotAPITokenAuthInfo.from_dict(obj)
+ case "user": return UserAuthInfo.from_dict(obj)
+ case "gh-cli": return GhCLIAuthInfo.from_dict(obj)
+ case "api-key": return APIKeyAuthInfo.from_dict(obj)
+ case _: raise ValueError(f"Unknown AuthInfo type: {kind!r}")
+
+# A content block within a tool result, which may be text, terminal output, image, audio, or a resource
+ExternalToolTextResultForLlmContent = ExternalToolTextResultForLlmContentText | ExternalToolTextResultForLlmContentTerminal | ExternalToolTextResultForLlmContentImage | ExternalToolTextResultForLlmContentAudio | ExternalToolTextResultForLlmContentResourceLink | ExternalToolTextResultForLlmContentResource
+
+def _load_ExternalToolTextResultForLlmContent(obj: Any) -> "ExternalToolTextResultForLlmContent":
+ assert isinstance(obj, dict)
+ kind = obj.get("type")
+ match kind:
+ case "text": return ExternalToolTextResultForLlmContentText.from_dict(obj)
+ case "terminal": return ExternalToolTextResultForLlmContentTerminal.from_dict(obj)
+ case "image": return ExternalToolTextResultForLlmContentImage.from_dict(obj)
+ case "audio": return ExternalToolTextResultForLlmContentAudio.from_dict(obj)
+ case "resource_link": return ExternalToolTextResultForLlmContentResourceLink.from_dict(obj)
+ case "resource": return ExternalToolTextResultForLlmContentResource.from_dict(obj)
+ case _: raise ValueError(f"Unknown ExternalToolTextResultForLlmContent type: {kind!r}")
+
+# The client's response to the pending permission prompt
+PermissionDecision = PermissionDecisionApproveOnce | PermissionDecisionApproveForSession | PermissionDecisionApproveForLocation | PermissionDecisionApprovePermanently | PermissionDecisionReject | PermissionDecisionUserNotAvailable | PermissionDecisionApproved | PermissionDecisionApprovedForSession | PermissionDecisionApprovedForLocation | PermissionDecisionCancelled | PermissionDecisionDeniedByRules | PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser | PermissionDecisionDeniedInteractivelyByUser | PermissionDecisionDeniedByContentExclusionPolicy | PermissionDecisionDeniedByPermissionRequestHook
+
+def _load_PermissionDecision(obj: Any) -> "PermissionDecision":
+ assert isinstance(obj, dict)
+ kind = obj.get("kind")
+ match kind:
+ case "approve-once": return PermissionDecisionApproveOnce.from_dict(obj)
+ case "approve-for-session": return PermissionDecisionApproveForSession.from_dict(obj)
+ case "approve-for-location": return PermissionDecisionApproveForLocation.from_dict(obj)
+ case "approve-permanently": return PermissionDecisionApprovePermanently.from_dict(obj)
+ case "reject": return PermissionDecisionReject.from_dict(obj)
+ case "user-not-available": return PermissionDecisionUserNotAvailable.from_dict(obj)
+ case "approved": return PermissionDecisionApproved.from_dict(obj)
+ case "approved-for-session": return PermissionDecisionApprovedForSession.from_dict(obj)
+ case "approved-for-location": return PermissionDecisionApprovedForLocation.from_dict(obj)
+ case "cancelled": return PermissionDecisionCancelled.from_dict(obj)
+ case "denied-by-rules": return PermissionDecisionDeniedByRules.from_dict(obj)
+ case "denied-no-approval-rule-and-could-not-request-from-user": return PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser.from_dict(obj)
+ case "denied-interactively-by-user": return PermissionDecisionDeniedInteractivelyByUser.from_dict(obj)
+ case "denied-by-content-exclusion-policy": return PermissionDecisionDeniedByContentExclusionPolicy.from_dict(obj)
+ case "denied-by-permission-request-hook": return PermissionDecisionDeniedByPermissionRequestHook.from_dict(obj)
+ case _: raise ValueError(f"Unknown PermissionDecision kind: {kind!r}")
+
+# Approval to persist for this location
+PermissionDecisionApproveForLocationApproval = PermissionDecisionApproveForLocationApprovalCommands | PermissionDecisionApproveForLocationApprovalRead | PermissionDecisionApproveForLocationApprovalWrite | PermissionDecisionApproveForLocationApprovalMCP | PermissionDecisionApproveForLocationApprovalMCPSampling | PermissionDecisionApproveForLocationApprovalMemory | PermissionDecisionApproveForLocationApprovalCustomTool | PermissionDecisionApproveForLocationApprovalExtensionManagement | PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess
+
+def _load_PermissionDecisionApproveForLocationApproval(obj: Any) -> "PermissionDecisionApproveForLocationApproval":
+ assert isinstance(obj, dict)
+ kind = obj.get("kind")
+ match kind:
+ case "commands": return PermissionDecisionApproveForLocationApprovalCommands.from_dict(obj)
+ case "read": return PermissionDecisionApproveForLocationApprovalRead.from_dict(obj)
+ case "write": return PermissionDecisionApproveForLocationApprovalWrite.from_dict(obj)
+ case "mcp": return PermissionDecisionApproveForLocationApprovalMCP.from_dict(obj)
+ case "mcp-sampling": return PermissionDecisionApproveForLocationApprovalMCPSampling.from_dict(obj)
+ case "memory": return PermissionDecisionApproveForLocationApprovalMemory.from_dict(obj)
+ case "custom-tool": return PermissionDecisionApproveForLocationApprovalCustomTool.from_dict(obj)
+ case "extension-management": return PermissionDecisionApproveForLocationApprovalExtensionManagement.from_dict(obj)
+ case "extension-permission-access": return PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess.from_dict(obj)
+ case _: raise ValueError(f"Unknown PermissionDecisionApproveForLocationApproval kind: {kind!r}")
+
+# Session-scoped approval to remember (tool prompts only; omitted for path/url prompts)
+PermissionDecisionApproveForSessionApproval = PermissionDecisionApproveForSessionApprovalCommands | PermissionDecisionApproveForSessionApprovalRead | PermissionDecisionApproveForSessionApprovalWrite | PermissionDecisionApproveForSessionApprovalMCP | PermissionDecisionApproveForSessionApprovalMCPSampling | PermissionDecisionApproveForSessionApprovalMemory | PermissionDecisionApproveForSessionApprovalCustomTool | PermissionDecisionApproveForSessionApprovalExtensionManagement | PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess
+
+def _load_PermissionDecisionApproveForSessionApproval(obj: Any) -> "PermissionDecisionApproveForSessionApproval":
+ assert isinstance(obj, dict)
+ kind = obj.get("kind")
+ match kind:
+ case "commands": return PermissionDecisionApproveForSessionApprovalCommands.from_dict(obj)
+ case "read": return PermissionDecisionApproveForSessionApprovalRead.from_dict(obj)
+ case "write": return PermissionDecisionApproveForSessionApprovalWrite.from_dict(obj)
+ case "mcp": return PermissionDecisionApproveForSessionApprovalMCP.from_dict(obj)
+ case "mcp-sampling": return PermissionDecisionApproveForSessionApprovalMCPSampling.from_dict(obj)
+ case "memory": return PermissionDecisionApproveForSessionApprovalMemory.from_dict(obj)
+ case "custom-tool": return PermissionDecisionApproveForSessionApprovalCustomTool.from_dict(obj)
+ case "extension-management": return PermissionDecisionApproveForSessionApprovalExtensionManagement.from_dict(obj)
+ case "extension-permission-access": return PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess.from_dict(obj)
+ case _: raise ValueError(f"Unknown PermissionDecisionApproveForSessionApproval kind: {kind!r}")
+
+# Tool approval to persist and apply
+PermissionsLocationsAddToolApprovalDetails = PermissionsLocationsAddToolApprovalDetailsCommands | PermissionsLocationsAddToolApprovalDetailsRead | PermissionsLocationsAddToolApprovalDetailsWrite | PermissionsLocationsAddToolApprovalDetailsMCP | PermissionsLocationsAddToolApprovalDetailsMCPSampling | PermissionsLocationsAddToolApprovalDetailsMemory | PermissionsLocationsAddToolApprovalDetailsCustomTool | PermissionsLocationsAddToolApprovalDetailsExtensionManagement | PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess
+
+def _load_PermissionsLocationsAddToolApprovalDetails(obj: Any) -> "PermissionsLocationsAddToolApprovalDetails":
+ assert isinstance(obj, dict)
+ kind = obj.get("kind")
+ match kind:
+ case "commands": return PermissionsLocationsAddToolApprovalDetailsCommands.from_dict(obj)
+ case "read": return PermissionsLocationsAddToolApprovalDetailsRead.from_dict(obj)
+ case "write": return PermissionsLocationsAddToolApprovalDetailsWrite.from_dict(obj)
+ case "mcp": return PermissionsLocationsAddToolApprovalDetailsMCP.from_dict(obj)
+ case "mcp-sampling": return PermissionsLocationsAddToolApprovalDetailsMCPSampling.from_dict(obj)
+ case "memory": return PermissionsLocationsAddToolApprovalDetailsMemory.from_dict(obj)
+ case "custom-tool": return PermissionsLocationsAddToolApprovalDetailsCustomTool.from_dict(obj)
+ case "extension-management": return PermissionsLocationsAddToolApprovalDetailsExtensionManagement.from_dict(obj)
+ case "extension-permission-access": return PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess.from_dict(obj)
+ case _: raise ValueError(f"Unknown PermissionsLocationsAddToolApprovalDetails kind: {kind!r}")
+
+# Result of the queued command execution.
+QueuedCommandResult = QueuedCommandHandled | QueuedCommandNotHandled
+
+def _load_QueuedCommandResult(obj: Any) -> "QueuedCommandResult":
+ assert isinstance(obj, dict)
+ kind = obj.get("handled")
+ match kind:
+ case "true": return QueuedCommandHandled.from_dict(obj)
+ case "false": return QueuedCommandNotHandled.from_dict(obj)
+ case _: raise ValueError(f"Unknown QueuedCommandResult handled: {kind!r}")
+
+# A user message attachment — a file, directory, code selection, blob, or GitHub reference
+SendAttachment = SendAttachmentFile | SendAttachmentDirectory | SendAttachmentSelection | SendAttachmentGithubReference | SendAttachmentBlob
+
+def _load_SendAttachment(obj: Any) -> "SendAttachment":
+ assert isinstance(obj, dict)
+ kind = obj.get("type")
+ match kind:
+ case "file": return SendAttachmentFile.from_dict(obj)
+ case "directory": return SendAttachmentDirectory.from_dict(obj)
+ case "selection": return SendAttachmentSelection.from_dict(obj)
+ case "github_reference": return SendAttachmentGithubReference.from_dict(obj)
+ case "blob": return SendAttachmentBlob.from_dict(obj)
+ case _: raise ValueError(f"Unknown SendAttachment type: {kind!r}")
+
+# Result of invoking the slash command (text output, prompt to send to the agent, or completion).
+SlashCommandInvocationResult = SlashCommandTextResult | SlashCommandAgentPromptResult | SlashCommandCompletedResult | SlashCommandSelectSubcommandResult
+
+def _load_SlashCommandInvocationResult(obj: Any) -> "SlashCommandInvocationResult":
+ assert isinstance(obj, dict)
+ kind = obj.get("kind")
+ match kind:
+ case "text": return SlashCommandTextResult.from_dict(obj)
+ case "agent-prompt": return SlashCommandAgentPromptResult.from_dict(obj)
+ case "completed": return SlashCommandCompletedResult.from_dict(obj)
+ case "select-subcommand": return SlashCommandSelectSubcommandResult.from_dict(obj)
+ case _: raise ValueError(f"Unknown SlashCommandInvocationResult kind: {kind!r}")
+
+# Schema for the `TaskInfo` type.
+TaskInfo = TaskAgentInfo | TaskShellInfo
+
+def _load_TaskInfo(obj: Any) -> "TaskInfo":
+ assert isinstance(obj, dict)
+ kind = obj.get("type")
+ match kind:
+ case "agent": return TaskAgentInfo.from_dict(obj)
+ case "shell": return TaskShellInfo.from_dict(obj)
+ case _: raise ValueError(f"Unknown TaskInfo type: {kind!r}")
+
ExternalToolResult = ExternalToolTextResultForLlm
FilterMapping = dict
@@ -17261,7 +16117,7 @@ async def invoke(self, params: CommandsInvokeRequest, *, timeout: float | None =
"Invokes a slash command in the session.\n\nArgs:\n params: Slash command name and optional raw input string to invoke.\n\nReturns:\n Result of invoking the slash command (text output, prompt to send to the agent, or completion)."
params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None}
params_dict["sessionId"] = self._session_id
- return SlashCommandInvocationResult.from_dict(await self._client.request("session.commands.invoke", params_dict, **_timeout_kwargs(timeout)))
+ return _load_SlashCommandInvocationResult(await self._client.request("session.commands.invoke", params_dict, **_timeout_kwargs(timeout)))
async def handle_pending_command(self, params: CommandsHandlePendingCommandRequest, *, timeout: float | None = None) -> CommandsHandlePendingCommandResult:
"Reports completion of a pending client-handled slash command.\n\nArgs:\n params: Pending command request ID and an optional error if the client handler failed.\n\nReturns:\n Indicates whether the pending client-handled command was completed successfully."
diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py
index 515f9c317..4b72621df 100644
--- a/python/copilot/generated/session_events.py
+++ b/python/copilot/generated/session_events.py
@@ -9,7 +9,7 @@
from dataclasses import dataclass
from datetime import datetime, timedelta
from enum import Enum
-from typing import Any, TypeVar, cast
+from typing import Any, ClassVar, TypeVar, cast
from uuid import UUID
import dateutil.parser
@@ -1812,6 +1812,91 @@ def to_dict(self) -> dict:
return {}
+@dataclass
+class PermissionApproved:
+ "Schema for the `PermissionApproved` type."
+ kind: ClassVar[str] = "approved"
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionApproved":
+ assert isinstance(obj, dict)
+ return PermissionApproved(
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = self.kind
+ return result
+
+
+@dataclass
+class PermissionApprovedForLocation:
+ "Schema for the `PermissionApprovedForLocation` type."
+ approval: UserToolSessionApproval
+ kind: ClassVar[str] = "approved-for-location"
+ location_key: str
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionApprovedForLocation":
+ assert isinstance(obj, dict)
+ approval = _load_UserToolSessionApproval(obj.get("approval"))
+ location_key = from_str(obj.get("locationKey"))
+ return PermissionApprovedForLocation(
+ approval=approval,
+ location_key=location_key,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["approval"] = self.approval.to_dict()
+ result["kind"] = self.kind
+ result["locationKey"] = from_str(self.location_key)
+ return result
+
+
+@dataclass
+class PermissionApprovedForSession:
+ "Schema for the `PermissionApprovedForSession` type."
+ approval: UserToolSessionApproval
+ kind: ClassVar[str] = "approved-for-session"
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionApprovedForSession":
+ assert isinstance(obj, dict)
+ approval = _load_UserToolSessionApproval(obj.get("approval"))
+ return PermissionApprovedForSession(
+ approval=approval,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["approval"] = self.approval.to_dict()
+ result["kind"] = self.kind
+ return result
+
+
+@dataclass
+class PermissionCancelled:
+ "Schema for the `PermissionCancelled` type."
+ kind: ClassVar[str] = "cancelled"
+ reason: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionCancelled":
+ assert isinstance(obj, dict)
+ reason = from_union([from_none, from_str], obj.get("reason"))
+ return PermissionCancelled(
+ reason=reason,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = self.kind
+ if self.reason is not None:
+ result["reason"] = from_union([from_none, from_str], self.reason)
+ return result
+
+
@dataclass
class PermissionCompletedData:
"Permission request completion notification signaling UI dismissal"
@@ -1822,357 +1907,845 @@ class PermissionCompletedData:
@staticmethod
def from_dict(obj: Any) -> "PermissionCompletedData":
assert isinstance(obj, dict)
- request_id = from_str(obj.get("requestId"))
- result = PermissionResult.from_dict(obj.get("result"))
+ request_id = from_str(obj.get("requestId"))
+ result = _load_PermissionResult(obj.get("result"))
+ tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
+ return PermissionCompletedData(
+ request_id=request_id,
+ result=result,
+ tool_call_id=tool_call_id,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["requestId"] = from_str(self.request_id)
+ result["result"] = self.result.to_dict()
+ if self.tool_call_id is not None:
+ result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
+ return result
+
+
+@dataclass
+class PermissionDeniedByContentExclusionPolicy:
+ "Schema for the `PermissionDeniedByContentExclusionPolicy` type."
+ kind: ClassVar[str] = "denied-by-content-exclusion-policy"
+ message: str
+ path: str
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionDeniedByContentExclusionPolicy":
+ assert isinstance(obj, dict)
+ message = from_str(obj.get("message"))
+ path = from_str(obj.get("path"))
+ return PermissionDeniedByContentExclusionPolicy(
+ message=message,
+ path=path,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = self.kind
+ result["message"] = from_str(self.message)
+ result["path"] = from_str(self.path)
+ return result
+
+
+@dataclass
+class PermissionDeniedByPermissionRequestHook:
+ "Schema for the `PermissionDeniedByPermissionRequestHook` type."
+ kind: ClassVar[str] = "denied-by-permission-request-hook"
+ interrupt: bool | None = None
+ message: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionDeniedByPermissionRequestHook":
+ assert isinstance(obj, dict)
+ interrupt = from_union([from_none, from_bool], obj.get("interrupt"))
+ message = from_union([from_none, from_str], obj.get("message"))
+ return PermissionDeniedByPermissionRequestHook(
+ interrupt=interrupt,
+ message=message,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = self.kind
+ if self.interrupt is not None:
+ result["interrupt"] = from_union([from_none, from_bool], self.interrupt)
+ if self.message is not None:
+ result["message"] = from_union([from_none, from_str], self.message)
+ return result
+
+
+@dataclass
+class PermissionDeniedByRules:
+ "Schema for the `PermissionDeniedByRules` type."
+ kind: ClassVar[str] = "denied-by-rules"
+ rules: list[PermissionRule]
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionDeniedByRules":
+ assert isinstance(obj, dict)
+ rules = from_list(PermissionRule.from_dict, obj.get("rules"))
+ return PermissionDeniedByRules(
+ rules=rules,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = self.kind
+ result["rules"] = from_list(lambda x: to_class(PermissionRule, x), self.rules)
+ return result
+
+
+@dataclass
+class PermissionDeniedInteractivelyByUser:
+ "Schema for the `PermissionDeniedInteractivelyByUser` type."
+ kind: ClassVar[str] = "denied-interactively-by-user"
+ feedback: str | None = None
+ force_reject: bool | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionDeniedInteractivelyByUser":
+ assert isinstance(obj, dict)
+ feedback = from_union([from_none, from_str], obj.get("feedback"))
+ force_reject = from_union([from_none, from_bool], obj.get("forceReject"))
+ return PermissionDeniedInteractivelyByUser(
+ feedback=feedback,
+ force_reject=force_reject,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = self.kind
+ if self.feedback is not None:
+ result["feedback"] = from_union([from_none, from_str], self.feedback)
+ if self.force_reject is not None:
+ result["forceReject"] = from_union([from_none, from_bool], self.force_reject)
+ return result
+
+
+@dataclass
+class PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser:
+ "Schema for the `PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser` type."
+ kind: ClassVar[str] = "denied-no-approval-rule-and-could-not-request-from-user"
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser":
+ assert isinstance(obj, dict)
+ return PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser(
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = self.kind
+ return result
+
+
+@dataclass
+class PermissionPromptRequestCommands:
+ "Shell command permission prompt"
+ can_offer_session_approval: bool
+ command_identifiers: list[str]
+ full_command_text: str
+ intention: str
+ kind: ClassVar[str] = "commands"
+ tool_call_id: str | None = None
+ warning: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionPromptRequestCommands":
+ assert isinstance(obj, dict)
+ can_offer_session_approval = from_bool(obj.get("canOfferSessionApproval"))
+ command_identifiers = from_list(from_str, obj.get("commandIdentifiers"))
+ full_command_text = from_str(obj.get("fullCommandText"))
+ intention = from_str(obj.get("intention"))
+ tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
+ warning = from_union([from_none, from_str], obj.get("warning"))
+ return PermissionPromptRequestCommands(
+ can_offer_session_approval=can_offer_session_approval,
+ command_identifiers=command_identifiers,
+ full_command_text=full_command_text,
+ intention=intention,
+ tool_call_id=tool_call_id,
+ warning=warning,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["canOfferSessionApproval"] = from_bool(self.can_offer_session_approval)
+ result["commandIdentifiers"] = from_list(from_str, self.command_identifiers)
+ result["fullCommandText"] = from_str(self.full_command_text)
+ result["intention"] = from_str(self.intention)
+ result["kind"] = self.kind
+ if self.tool_call_id is not None:
+ result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
+ if self.warning is not None:
+ result["warning"] = from_union([from_none, from_str], self.warning)
+ return result
+
+
+@dataclass
+class PermissionPromptRequestCustomTool:
+ "Custom tool invocation permission prompt"
+ kind: ClassVar[str] = "custom-tool"
+ tool_description: str
+ tool_name: str
+ args: Any = None
+ tool_call_id: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionPromptRequestCustomTool":
+ assert isinstance(obj, dict)
+ tool_description = from_str(obj.get("toolDescription"))
+ tool_name = from_str(obj.get("toolName"))
+ args = obj.get("args")
+ tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
+ return PermissionPromptRequestCustomTool(
+ tool_description=tool_description,
+ tool_name=tool_name,
+ args=args,
+ tool_call_id=tool_call_id,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = self.kind
+ result["toolDescription"] = from_str(self.tool_description)
+ result["toolName"] = from_str(self.tool_name)
+ if self.args is not None:
+ result["args"] = self.args
+ if self.tool_call_id is not None:
+ result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
+ return result
+
+
+@dataclass
+class PermissionPromptRequestExtensionManagement:
+ "Extension management permission prompt"
+ kind: ClassVar[str] = "extension-management"
+ operation: str
+ extension_name: str | None = None
+ tool_call_id: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionPromptRequestExtensionManagement":
+ assert isinstance(obj, dict)
+ operation = from_str(obj.get("operation"))
+ extension_name = from_union([from_none, from_str], obj.get("extensionName"))
+ tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
+ return PermissionPromptRequestExtensionManagement(
+ operation=operation,
+ extension_name=extension_name,
+ tool_call_id=tool_call_id,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = self.kind
+ result["operation"] = from_str(self.operation)
+ if self.extension_name is not None:
+ result["extensionName"] = from_union([from_none, from_str], self.extension_name)
+ if self.tool_call_id is not None:
+ result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
+ return result
+
+
+@dataclass
+class PermissionPromptRequestExtensionPermissionAccess:
+ "Extension permission access prompt"
+ capabilities: list[str]
+ extension_name: str
+ kind: ClassVar[str] = "extension-permission-access"
+ tool_call_id: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionPromptRequestExtensionPermissionAccess":
+ assert isinstance(obj, dict)
+ capabilities = from_list(from_str, obj.get("capabilities"))
+ extension_name = from_str(obj.get("extensionName"))
+ tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
+ return PermissionPromptRequestExtensionPermissionAccess(
+ capabilities=capabilities,
+ extension_name=extension_name,
+ tool_call_id=tool_call_id,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["capabilities"] = from_list(from_str, self.capabilities)
+ result["extensionName"] = from_str(self.extension_name)
+ result["kind"] = self.kind
+ if self.tool_call_id is not None:
+ result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
+ return result
+
+
+@dataclass
+class PermissionPromptRequestHook:
+ "Hook confirmation permission prompt"
+ kind: ClassVar[str] = "hook"
+ tool_name: str
+ hook_message: str | None = None
+ tool_args: Any = None
+ tool_call_id: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionPromptRequestHook":
+ assert isinstance(obj, dict)
+ tool_name = from_str(obj.get("toolName"))
+ hook_message = from_union([from_none, from_str], obj.get("hookMessage"))
+ tool_args = obj.get("toolArgs")
+ tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
+ return PermissionPromptRequestHook(
+ tool_name=tool_name,
+ hook_message=hook_message,
+ tool_args=tool_args,
+ tool_call_id=tool_call_id,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = self.kind
+ result["toolName"] = from_str(self.tool_name)
+ if self.hook_message is not None:
+ result["hookMessage"] = from_union([from_none, from_str], self.hook_message)
+ if self.tool_args is not None:
+ result["toolArgs"] = self.tool_args
+ if self.tool_call_id is not None:
+ result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
+ return result
+
+
+@dataclass
+class PermissionPromptRequestMcp:
+ "MCP tool invocation permission prompt"
+ kind: ClassVar[str] = "mcp"
+ server_name: str
+ tool_name: str
+ tool_title: str
+ args: Any | None = None
+ tool_call_id: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionPromptRequestMcp":
+ assert isinstance(obj, dict)
+ server_name = from_str(obj.get("serverName"))
+ tool_name = from_str(obj.get("toolName"))
+ tool_title = from_str(obj.get("toolTitle"))
+ args = from_union([from_none, lambda x: x], obj.get("args"))
+ tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
+ return PermissionPromptRequestMcp(
+ server_name=server_name,
+ tool_name=tool_name,
+ tool_title=tool_title,
+ args=args,
+ tool_call_id=tool_call_id,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = self.kind
+ result["serverName"] = from_str(self.server_name)
+ result["toolName"] = from_str(self.tool_name)
+ result["toolTitle"] = from_str(self.tool_title)
+ if self.args is not None:
+ result["args"] = from_union([from_none, lambda x: x], self.args)
+ if self.tool_call_id is not None:
+ result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
+ return result
+
+
+@dataclass
+class PermissionPromptRequestMemory:
+ "Memory operation permission prompt"
+ fact: str
+ kind: ClassVar[str] = "memory"
+ action: PermissionRequestMemoryAction | None = None
+ citations: str | None = None
+ direction: PermissionRequestMemoryDirection | None = None
+ reason: str | None = None
+ subject: str | None = None
+ tool_call_id: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionPromptRequestMemory":
+ assert isinstance(obj, dict)
+ fact = from_str(obj.get("fact"))
+ action = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryAction, x)], obj.get("action"))
+ citations = from_union([from_none, from_str], obj.get("citations"))
+ direction = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryDirection, x)], obj.get("direction"))
+ reason = from_union([from_none, from_str], obj.get("reason"))
+ subject = from_union([from_none, from_str], obj.get("subject"))
+ tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
+ return PermissionPromptRequestMemory(
+ fact=fact,
+ action=action,
+ citations=citations,
+ direction=direction,
+ reason=reason,
+ subject=subject,
+ tool_call_id=tool_call_id,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["fact"] = from_str(self.fact)
+ result["kind"] = self.kind
+ if self.action is not None:
+ result["action"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryAction, x)], self.action)
+ if self.citations is not None:
+ result["citations"] = from_union([from_none, from_str], self.citations)
+ if self.direction is not None:
+ result["direction"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryDirection, x)], self.direction)
+ if self.reason is not None:
+ result["reason"] = from_union([from_none, from_str], self.reason)
+ if self.subject is not None:
+ result["subject"] = from_union([from_none, from_str], self.subject)
+ if self.tool_call_id is not None:
+ result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
+ return result
+
+
+@dataclass
+class PermissionPromptRequestPath:
+ "Path access permission prompt"
+ access_kind: PermissionPromptRequestPathAccessKind
+ kind: ClassVar[str] = "path"
+ paths: list[str]
+ tool_call_id: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionPromptRequestPath":
+ assert isinstance(obj, dict)
+ access_kind = parse_enum(PermissionPromptRequestPathAccessKind, obj.get("accessKind"))
+ paths = from_list(from_str, obj.get("paths"))
+ tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
+ return PermissionPromptRequestPath(
+ access_kind=access_kind,
+ paths=paths,
+ tool_call_id=tool_call_id,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["accessKind"] = to_enum(PermissionPromptRequestPathAccessKind, self.access_kind)
+ result["kind"] = self.kind
+ result["paths"] = from_list(from_str, self.paths)
+ if self.tool_call_id is not None:
+ result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
+ return result
+
+
+@dataclass
+class PermissionPromptRequestRead:
+ "File read permission prompt"
+ intention: str
+ kind: ClassVar[str] = "read"
+ path: str
+ tool_call_id: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionPromptRequestRead":
+ assert isinstance(obj, dict)
+ intention = from_str(obj.get("intention"))
+ path = from_str(obj.get("path"))
+ tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
+ return PermissionPromptRequestRead(
+ intention=intention,
+ path=path,
+ tool_call_id=tool_call_id,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["intention"] = from_str(self.intention)
+ result["kind"] = self.kind
+ result["path"] = from_str(self.path)
+ if self.tool_call_id is not None:
+ result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
+ return result
+
+
+@dataclass
+class PermissionPromptRequestUrl:
+ "URL access permission prompt"
+ intention: str
+ kind: ClassVar[str] = "url"
+ url: str
+ tool_call_id: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionPromptRequestUrl":
+ assert isinstance(obj, dict)
+ intention = from_str(obj.get("intention"))
+ url = from_str(obj.get("url"))
+ tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
+ return PermissionPromptRequestUrl(
+ intention=intention,
+ url=url,
+ tool_call_id=tool_call_id,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["intention"] = from_str(self.intention)
+ result["kind"] = self.kind
+ result["url"] = from_str(self.url)
+ if self.tool_call_id is not None:
+ result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
+ return result
+
+
+@dataclass
+class PermissionPromptRequestWrite:
+ "File write permission prompt"
+ can_offer_session_approval: bool
+ diff: str
+ file_name: str
+ intention: str
+ kind: ClassVar[str] = "write"
+ new_file_contents: str | None = None
+ tool_call_id: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionPromptRequestWrite":
+ assert isinstance(obj, dict)
+ can_offer_session_approval = from_bool(obj.get("canOfferSessionApproval"))
+ diff = from_str(obj.get("diff"))
+ file_name = from_str(obj.get("fileName"))
+ intention = from_str(obj.get("intention"))
+ new_file_contents = from_union([from_none, from_str], obj.get("newFileContents"))
+ tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
+ return PermissionPromptRequestWrite(
+ can_offer_session_approval=can_offer_session_approval,
+ diff=diff,
+ file_name=file_name,
+ intention=intention,
+ new_file_contents=new_file_contents,
+ tool_call_id=tool_call_id,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["canOfferSessionApproval"] = from_bool(self.can_offer_session_approval)
+ result["diff"] = from_str(self.diff)
+ result["fileName"] = from_str(self.file_name)
+ result["intention"] = from_str(self.intention)
+ result["kind"] = self.kind
+ if self.new_file_contents is not None:
+ result["newFileContents"] = from_union([from_none, from_str], self.new_file_contents)
+ if self.tool_call_id is not None:
+ result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
+ return result
+
+
+@dataclass
+class PermissionRequestCustomTool:
+ "Custom tool invocation permission request"
+ kind: ClassVar[str] = "custom-tool"
+ tool_description: str
+ tool_name: str
+ args: Any = None
+ tool_call_id: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionRequestCustomTool":
+ assert isinstance(obj, dict)
+ tool_description = from_str(obj.get("toolDescription"))
+ tool_name = from_str(obj.get("toolName"))
+ args = obj.get("args")
+ tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
+ return PermissionRequestCustomTool(
+ tool_description=tool_description,
+ tool_name=tool_name,
+ args=args,
+ tool_call_id=tool_call_id,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = self.kind
+ result["toolDescription"] = from_str(self.tool_description)
+ result["toolName"] = from_str(self.tool_name)
+ if self.args is not None:
+ result["args"] = self.args
+ if self.tool_call_id is not None:
+ result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
+ return result
+
+
+@dataclass
+class PermissionRequestExtensionManagement:
+ "Extension management permission request"
+ kind: ClassVar[str] = "extension-management"
+ operation: str
+ extension_name: str | None = None
+ tool_call_id: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionRequestExtensionManagement":
+ assert isinstance(obj, dict)
+ operation = from_str(obj.get("operation"))
+ extension_name = from_union([from_none, from_str], obj.get("extensionName"))
+ tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
+ return PermissionRequestExtensionManagement(
+ operation=operation,
+ extension_name=extension_name,
+ tool_call_id=tool_call_id,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = self.kind
+ result["operation"] = from_str(self.operation)
+ if self.extension_name is not None:
+ result["extensionName"] = from_union([from_none, from_str], self.extension_name)
+ if self.tool_call_id is not None:
+ result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
+ return result
+
+
+@dataclass
+class PermissionRequestExtensionPermissionAccess:
+ "Extension permission access request"
+ capabilities: list[str]
+ extension_name: str
+ kind: ClassVar[str] = "extension-permission-access"
+ tool_call_id: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionRequestExtensionPermissionAccess":
+ assert isinstance(obj, dict)
+ capabilities = from_list(from_str, obj.get("capabilities"))
+ extension_name = from_str(obj.get("extensionName"))
+ tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
+ return PermissionRequestExtensionPermissionAccess(
+ capabilities=capabilities,
+ extension_name=extension_name,
+ tool_call_id=tool_call_id,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["capabilities"] = from_list(from_str, self.capabilities)
+ result["extensionName"] = from_str(self.extension_name)
+ result["kind"] = self.kind
+ if self.tool_call_id is not None:
+ result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
+ return result
+
+
+@dataclass
+class PermissionRequestHook:
+ "Hook confirmation permission request"
+ kind: ClassVar[str] = "hook"
+ tool_name: str
+ hook_message: str | None = None
+ tool_args: Any = None
+ tool_call_id: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionRequestHook":
+ assert isinstance(obj, dict)
+ tool_name = from_str(obj.get("toolName"))
+ hook_message = from_union([from_none, from_str], obj.get("hookMessage"))
+ tool_args = obj.get("toolArgs")
+ tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
+ return PermissionRequestHook(
+ tool_name=tool_name,
+ hook_message=hook_message,
+ tool_args=tool_args,
+ tool_call_id=tool_call_id,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = self.kind
+ result["toolName"] = from_str(self.tool_name)
+ if self.hook_message is not None:
+ result["hookMessage"] = from_union([from_none, from_str], self.hook_message)
+ if self.tool_args is not None:
+ result["toolArgs"] = self.tool_args
+ if self.tool_call_id is not None:
+ result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
+ return result
+
+
+@dataclass
+class PermissionRequestMcp:
+ "MCP tool invocation permission request"
+ kind: ClassVar[str] = "mcp"
+ read_only: bool
+ server_name: str
+ tool_name: str
+ tool_title: str
+ args: Any = None
+ tool_call_id: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionRequestMcp":
+ assert isinstance(obj, dict)
+ read_only = from_bool(obj.get("readOnly"))
+ server_name = from_str(obj.get("serverName"))
+ tool_name = from_str(obj.get("toolName"))
+ tool_title = from_str(obj.get("toolTitle"))
+ args = obj.get("args")
tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
- return PermissionCompletedData(
- request_id=request_id,
- result=result,
+ return PermissionRequestMcp(
+ read_only=read_only,
+ server_name=server_name,
+ tool_name=tool_name,
+ tool_title=tool_title,
+ args=args,
tool_call_id=tool_call_id,
)
def to_dict(self) -> dict:
result: dict = {}
- result["requestId"] = from_str(self.request_id)
- result["result"] = to_class(PermissionResult, self.result)
+ result["kind"] = self.kind
+ result["readOnly"] = from_bool(self.read_only)
+ result["serverName"] = from_str(self.server_name)
+ result["toolName"] = from_str(self.tool_name)
+ result["toolTitle"] = from_str(self.tool_title)
+ if self.args is not None:
+ result["args"] = self.args
if self.tool_call_id is not None:
result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
return result
@dataclass
-class PermissionPromptRequest:
- "Derived user-facing permission prompt details for UI consumers"
- kind: PermissionPromptRequestKind
- access_kind: PermissionPromptRequestPathAccessKind | None = None
+class PermissionRequestMemory:
+ "Memory operation permission request"
+ fact: str
+ kind: ClassVar[str] = "memory"
action: PermissionRequestMemoryAction | None = None
- args: Any | None = None
- can_offer_session_approval: bool | None = None
- capabilities: list[str] | None = None
citations: str | None = None
- command_identifiers: list[str] | None = None
- diff: str | None = None
direction: PermissionRequestMemoryDirection | None = None
- extension_name: str | None = None
- fact: str | None = None
- file_name: str | None = None
- full_command_text: str | None = None
- hook_message: str | None = None
- intention: str | None = None
- new_file_contents: str | None = None
- operation: str | None = None
- path: str | None = None
- paths: list[str] | None = None
reason: str | None = None
- server_name: str | None = None
subject: str | None = None
- tool_args: Any = None
tool_call_id: str | None = None
- tool_description: str | None = None
- tool_name: str | None = None
- tool_title: str | None = None
- url: str | None = None
- warning: str | None = None
@staticmethod
- def from_dict(obj: Any) -> "PermissionPromptRequest":
+ def from_dict(obj: Any) -> "PermissionRequestMemory":
assert isinstance(obj, dict)
- kind = parse_enum(PermissionPromptRequestKind, obj.get("kind"))
- access_kind = from_union([from_none, lambda x: parse_enum(PermissionPromptRequestPathAccessKind, x)], obj.get("accessKind"))
+ fact = from_str(obj.get("fact"))
action = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryAction, x)], obj.get("action"))
- args = from_union([from_none, lambda x: x], obj.get("args"))
- can_offer_session_approval = from_union([from_none, from_bool], obj.get("canOfferSessionApproval"))
- capabilities = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("capabilities"))
citations = from_union([from_none, from_str], obj.get("citations"))
- command_identifiers = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("commandIdentifiers"))
- diff = from_union([from_none, from_str], obj.get("diff"))
direction = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryDirection, x)], obj.get("direction"))
- extension_name = from_union([from_none, from_str], obj.get("extensionName"))
- fact = from_union([from_none, from_str], obj.get("fact"))
- file_name = from_union([from_none, from_str], obj.get("fileName"))
- full_command_text = from_union([from_none, from_str], obj.get("fullCommandText"))
- hook_message = from_union([from_none, from_str], obj.get("hookMessage"))
- intention = from_union([from_none, from_str], obj.get("intention"))
- new_file_contents = from_union([from_none, from_str], obj.get("newFileContents"))
- operation = from_union([from_none, from_str], obj.get("operation"))
- path = from_union([from_none, from_str], obj.get("path"))
- paths = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("paths"))
reason = from_union([from_none, from_str], obj.get("reason"))
- server_name = from_union([from_none, from_str], obj.get("serverName"))
subject = from_union([from_none, from_str], obj.get("subject"))
- tool_args = obj.get("toolArgs")
tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
- tool_description = from_union([from_none, from_str], obj.get("toolDescription"))
- tool_name = from_union([from_none, from_str], obj.get("toolName"))
- tool_title = from_union([from_none, from_str], obj.get("toolTitle"))
- url = from_union([from_none, from_str], obj.get("url"))
- warning = from_union([from_none, from_str], obj.get("warning"))
- return PermissionPromptRequest(
- kind=kind,
- access_kind=access_kind,
+ return PermissionRequestMemory(
+ fact=fact,
action=action,
- args=args,
- can_offer_session_approval=can_offer_session_approval,
- capabilities=capabilities,
citations=citations,
- command_identifiers=command_identifiers,
- diff=diff,
direction=direction,
- extension_name=extension_name,
- fact=fact,
- file_name=file_name,
- full_command_text=full_command_text,
- hook_message=hook_message,
- intention=intention,
- new_file_contents=new_file_contents,
- operation=operation,
- path=path,
- paths=paths,
reason=reason,
- server_name=server_name,
subject=subject,
- tool_args=tool_args,
tool_call_id=tool_call_id,
- tool_description=tool_description,
- tool_name=tool_name,
- tool_title=tool_title,
- url=url,
- warning=warning,
)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionPromptRequestKind, self.kind)
- if self.access_kind is not None:
- result["accessKind"] = from_union([from_none, lambda x: to_enum(PermissionPromptRequestPathAccessKind, x)], self.access_kind)
+ result["fact"] = from_str(self.fact)
+ result["kind"] = self.kind
if self.action is not None:
result["action"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryAction, x)], self.action)
- if self.args is not None:
- result["args"] = from_union([from_none, lambda x: x], self.args)
- if self.can_offer_session_approval is not None:
- result["canOfferSessionApproval"] = from_union([from_none, from_bool], self.can_offer_session_approval)
- if self.capabilities is not None:
- result["capabilities"] = from_union([from_none, lambda x: from_list(from_str, x)], self.capabilities)
if self.citations is not None:
result["citations"] = from_union([from_none, from_str], self.citations)
- if self.command_identifiers is not None:
- result["commandIdentifiers"] = from_union([from_none, lambda x: from_list(from_str, x)], self.command_identifiers)
- if self.diff is not None:
- result["diff"] = from_union([from_none, from_str], self.diff)
if self.direction is not None:
result["direction"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryDirection, x)], self.direction)
- if self.extension_name is not None:
- result["extensionName"] = from_union([from_none, from_str], self.extension_name)
- if self.fact is not None:
- result["fact"] = from_union([from_none, from_str], self.fact)
- if self.file_name is not None:
- result["fileName"] = from_union([from_none, from_str], self.file_name)
- if self.full_command_text is not None:
- result["fullCommandText"] = from_union([from_none, from_str], self.full_command_text)
- if self.hook_message is not None:
- result["hookMessage"] = from_union([from_none, from_str], self.hook_message)
- if self.intention is not None:
- result["intention"] = from_union([from_none, from_str], self.intention)
- if self.new_file_contents is not None:
- result["newFileContents"] = from_union([from_none, from_str], self.new_file_contents)
- if self.operation is not None:
- result["operation"] = from_union([from_none, from_str], self.operation)
- if self.path is not None:
- result["path"] = from_union([from_none, from_str], self.path)
- if self.paths is not None:
- result["paths"] = from_union([from_none, lambda x: from_list(from_str, x)], self.paths)
if self.reason is not None:
result["reason"] = from_union([from_none, from_str], self.reason)
- if self.server_name is not None:
- result["serverName"] = from_union([from_none, from_str], self.server_name)
if self.subject is not None:
result["subject"] = from_union([from_none, from_str], self.subject)
- if self.tool_args is not None:
- result["toolArgs"] = self.tool_args
if self.tool_call_id is not None:
result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
- if self.tool_description is not None:
- result["toolDescription"] = from_union([from_none, from_str], self.tool_description)
- if self.tool_name is not None:
- result["toolName"] = from_union([from_none, from_str], self.tool_name)
- if self.tool_title is not None:
- result["toolTitle"] = from_union([from_none, from_str], self.tool_title)
- if self.url is not None:
- result["url"] = from_union([from_none, from_str], self.url)
- if self.warning is not None:
- result["warning"] = from_union([from_none, from_str], self.warning)
return result
@dataclass
-class PermissionRequest:
- "Details of the permission being requested"
- kind: PermissionRequestKind
- action: PermissionRequestMemoryAction | None = None
- args: Any = None
- can_offer_session_approval: bool | None = None
- capabilities: list[str] | None = None
- citations: str | None = None
- commands: list[PermissionRequestShellCommand] | None = None
- diff: str | None = None
- direction: PermissionRequestMemoryDirection | None = None
- extension_name: str | None = None
- fact: str | None = None
- file_name: str | None = None
- full_command_text: str | None = None
- has_write_file_redirection: bool | None = None
- hook_message: str | None = None
- intention: str | None = None
- new_file_contents: str | None = None
- operation: str | None = None
- path: str | None = None
- possible_paths: list[str] | None = None
- possible_urls: list[PermissionRequestShellPossibleUrl] | None = None
- read_only: bool | None = None
- reason: str | None = None
- server_name: str | None = None
- subject: str | None = None
- tool_args: Any = None
+class PermissionRequestRead:
+ "File or directory read permission request"
+ intention: str
+ kind: ClassVar[str] = "read"
+ path: str
+ tool_call_id: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionRequestRead":
+ assert isinstance(obj, dict)
+ intention = from_str(obj.get("intention"))
+ path = from_str(obj.get("path"))
+ tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
+ return PermissionRequestRead(
+ intention=intention,
+ path=path,
+ tool_call_id=tool_call_id,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["intention"] = from_str(self.intention)
+ result["kind"] = self.kind
+ result["path"] = from_str(self.path)
+ if self.tool_call_id is not None:
+ result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
+ return result
+
+
+@dataclass
+class PermissionRequestShell:
+ "Shell command permission request"
+ can_offer_session_approval: bool
+ commands: list[PermissionRequestShellCommand]
+ full_command_text: str
+ has_write_file_redirection: bool
+ intention: str
+ kind: ClassVar[str] = "shell"
+ possible_paths: list[str]
+ possible_urls: list[PermissionRequestShellPossibleUrl]
tool_call_id: str | None = None
- tool_description: str | None = None
- tool_name: str | None = None
- tool_title: str | None = None
- url: str | None = None
warning: str | None = None
@staticmethod
- def from_dict(obj: Any) -> "PermissionRequest":
+ def from_dict(obj: Any) -> "PermissionRequestShell":
assert isinstance(obj, dict)
- kind = parse_enum(PermissionRequestKind, obj.get("kind"))
- action = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryAction, x)], obj.get("action"))
- args = obj.get("args")
- can_offer_session_approval = from_union([from_none, from_bool], obj.get("canOfferSessionApproval"))
- capabilities = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("capabilities"))
- citations = from_union([from_none, from_str], obj.get("citations"))
- commands = from_union([from_none, lambda x: from_list(PermissionRequestShellCommand.from_dict, x)], obj.get("commands"))
- diff = from_union([from_none, from_str], obj.get("diff"))
- direction = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryDirection, x)], obj.get("direction"))
- extension_name = from_union([from_none, from_str], obj.get("extensionName"))
- fact = from_union([from_none, from_str], obj.get("fact"))
- file_name = from_union([from_none, from_str], obj.get("fileName"))
- full_command_text = from_union([from_none, from_str], obj.get("fullCommandText"))
- has_write_file_redirection = from_union([from_none, from_bool], obj.get("hasWriteFileRedirection"))
- hook_message = from_union([from_none, from_str], obj.get("hookMessage"))
- intention = from_union([from_none, from_str], obj.get("intention"))
- new_file_contents = from_union([from_none, from_str], obj.get("newFileContents"))
- operation = from_union([from_none, from_str], obj.get("operation"))
- path = from_union([from_none, from_str], obj.get("path"))
- possible_paths = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("possiblePaths"))
- possible_urls = from_union([from_none, lambda x: from_list(PermissionRequestShellPossibleUrl.from_dict, x)], obj.get("possibleUrls"))
- read_only = from_union([from_none, from_bool], obj.get("readOnly"))
- reason = from_union([from_none, from_str], obj.get("reason"))
- server_name = from_union([from_none, from_str], obj.get("serverName"))
- subject = from_union([from_none, from_str], obj.get("subject"))
- tool_args = obj.get("toolArgs")
+ can_offer_session_approval = from_bool(obj.get("canOfferSessionApproval"))
+ commands = from_list(PermissionRequestShellCommand.from_dict, obj.get("commands"))
+ full_command_text = from_str(obj.get("fullCommandText"))
+ has_write_file_redirection = from_bool(obj.get("hasWriteFileRedirection"))
+ intention = from_str(obj.get("intention"))
+ possible_paths = from_list(from_str, obj.get("possiblePaths"))
+ possible_urls = from_list(PermissionRequestShellPossibleUrl.from_dict, obj.get("possibleUrls"))
tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
- tool_description = from_union([from_none, from_str], obj.get("toolDescription"))
- tool_name = from_union([from_none, from_str], obj.get("toolName"))
- tool_title = from_union([from_none, from_str], obj.get("toolTitle"))
- url = from_union([from_none, from_str], obj.get("url"))
warning = from_union([from_none, from_str], obj.get("warning"))
- return PermissionRequest(
- kind=kind,
- action=action,
- args=args,
+ return PermissionRequestShell(
can_offer_session_approval=can_offer_session_approval,
- capabilities=capabilities,
- citations=citations,
commands=commands,
- diff=diff,
- direction=direction,
- extension_name=extension_name,
- fact=fact,
- file_name=file_name,
full_command_text=full_command_text,
has_write_file_redirection=has_write_file_redirection,
- hook_message=hook_message,
intention=intention,
- new_file_contents=new_file_contents,
- operation=operation,
- path=path,
possible_paths=possible_paths,
possible_urls=possible_urls,
- read_only=read_only,
- reason=reason,
- server_name=server_name,
- subject=subject,
- tool_args=tool_args,
tool_call_id=tool_call_id,
- tool_description=tool_description,
- tool_name=tool_name,
- tool_title=tool_title,
- url=url,
warning=warning,
)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionRequestKind, self.kind)
- if self.action is not None:
- result["action"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryAction, x)], self.action)
- if self.args is not None:
- result["args"] = self.args
- if self.can_offer_session_approval is not None:
- result["canOfferSessionApproval"] = from_union([from_none, from_bool], self.can_offer_session_approval)
- if self.capabilities is not None:
- result["capabilities"] = from_union([from_none, lambda x: from_list(from_str, x)], self.capabilities)
- if self.citations is not None:
- result["citations"] = from_union([from_none, from_str], self.citations)
- if self.commands is not None:
- result["commands"] = from_union([from_none, lambda x: from_list(lambda x: to_class(PermissionRequestShellCommand, x), x)], self.commands)
- if self.diff is not None:
- result["diff"] = from_union([from_none, from_str], self.diff)
- if self.direction is not None:
- result["direction"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryDirection, x)], self.direction)
- if self.extension_name is not None:
- result["extensionName"] = from_union([from_none, from_str], self.extension_name)
- if self.fact is not None:
- result["fact"] = from_union([from_none, from_str], self.fact)
- if self.file_name is not None:
- result["fileName"] = from_union([from_none, from_str], self.file_name)
- if self.full_command_text is not None:
- result["fullCommandText"] = from_union([from_none, from_str], self.full_command_text)
- if self.has_write_file_redirection is not None:
- result["hasWriteFileRedirection"] = from_union([from_none, from_bool], self.has_write_file_redirection)
- if self.hook_message is not None:
- result["hookMessage"] = from_union([from_none, from_str], self.hook_message)
- if self.intention is not None:
- result["intention"] = from_union([from_none, from_str], self.intention)
- if self.new_file_contents is not None:
- result["newFileContents"] = from_union([from_none, from_str], self.new_file_contents)
- if self.operation is not None:
- result["operation"] = from_union([from_none, from_str], self.operation)
- if self.path is not None:
- result["path"] = from_union([from_none, from_str], self.path)
- if self.possible_paths is not None:
- result["possiblePaths"] = from_union([from_none, lambda x: from_list(from_str, x)], self.possible_paths)
- if self.possible_urls is not None:
- result["possibleUrls"] = from_union([from_none, lambda x: from_list(lambda x: to_class(PermissionRequestShellPossibleUrl, x), x)], self.possible_urls)
- if self.read_only is not None:
- result["readOnly"] = from_union([from_none, from_bool], self.read_only)
- if self.reason is not None:
- result["reason"] = from_union([from_none, from_str], self.reason)
- if self.server_name is not None:
- result["serverName"] = from_union([from_none, from_str], self.server_name)
- if self.subject is not None:
- result["subject"] = from_union([from_none, from_str], self.subject)
- if self.tool_args is not None:
- result["toolArgs"] = self.tool_args
+ result["canOfferSessionApproval"] = from_bool(self.can_offer_session_approval)
+ result["commands"] = from_list(lambda x: to_class(PermissionRequestShellCommand, x), self.commands)
+ result["fullCommandText"] = from_str(self.full_command_text)
+ result["hasWriteFileRedirection"] = from_bool(self.has_write_file_redirection)
+ result["intention"] = from_str(self.intention)
+ result["kind"] = self.kind
+ result["possiblePaths"] = from_list(from_str, self.possible_paths)
+ result["possibleUrls"] = from_list(lambda x: to_class(PermissionRequestShellPossibleUrl, x), self.possible_urls)
if self.tool_call_id is not None:
result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
- if self.tool_description is not None:
- result["toolDescription"] = from_union([from_none, from_str], self.tool_description)
- if self.tool_name is not None:
- result["toolName"] = from_union([from_none, from_str], self.tool_name)
- if self.tool_title is not None:
- result["toolTitle"] = from_union([from_none, from_str], self.tool_title)
- if self.url is not None:
- result["url"] = from_union([from_none, from_str], self.url)
if self.warning is not None:
result["warning"] = from_union([from_none, from_str], self.warning)
return result
@@ -2221,99 +2794,108 @@ def to_dict(self) -> dict:
@dataclass
-class PermissionRequestedData:
- "Permission request notification requiring client approval with request details"
- permission_request: PermissionRequest
- request_id: str
- prompt_request: PermissionPromptRequest | None = None
- resolved_by_hook: bool | None = None
+class PermissionRequestUrl:
+ "URL access permission request"
+ intention: str
+ kind: ClassVar[str] = "url"
+ url: str
+ tool_call_id: str | None = None
@staticmethod
- def from_dict(obj: Any) -> "PermissionRequestedData":
+ def from_dict(obj: Any) -> "PermissionRequestUrl":
assert isinstance(obj, dict)
- permission_request = PermissionRequest.from_dict(obj.get("permissionRequest"))
- request_id = from_str(obj.get("requestId"))
- prompt_request = from_union([from_none, PermissionPromptRequest.from_dict], obj.get("promptRequest"))
- resolved_by_hook = from_union([from_none, from_bool], obj.get("resolvedByHook"))
- return PermissionRequestedData(
- permission_request=permission_request,
- request_id=request_id,
- prompt_request=prompt_request,
- resolved_by_hook=resolved_by_hook,
+ intention = from_str(obj.get("intention"))
+ url = from_str(obj.get("url"))
+ tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
+ return PermissionRequestUrl(
+ intention=intention,
+ url=url,
+ tool_call_id=tool_call_id,
)
def to_dict(self) -> dict:
result: dict = {}
- result["permissionRequest"] = to_class(PermissionRequest, self.permission_request)
- result["requestId"] = from_str(self.request_id)
- if self.prompt_request is not None:
- result["promptRequest"] = from_union([from_none, lambda x: to_class(PermissionPromptRequest, x)], self.prompt_request)
- if self.resolved_by_hook is not None:
- result["resolvedByHook"] = from_union([from_none, from_bool], self.resolved_by_hook)
+ result["intention"] = from_str(self.intention)
+ result["kind"] = self.kind
+ result["url"] = from_str(self.url)
+ if self.tool_call_id is not None:
+ result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
return result
@dataclass
-class PermissionResult:
- "The result of the permission request"
- kind: PermissionResultKind
- approval: UserToolSessionApproval | None = None
- feedback: str | None = None
- force_reject: bool | None = None
- interrupt: bool | None = None
- location_key: str | None = None
- message: str | None = None
- path: str | None = None
- reason: str | None = None
- rules: list[PermissionRule] | None = None
+class PermissionRequestWrite:
+ "File write permission request"
+ can_offer_session_approval: bool
+ diff: str
+ file_name: str
+ intention: str
+ kind: ClassVar[str] = "write"
+ new_file_contents: str | None = None
+ tool_call_id: str | None = None
@staticmethod
- def from_dict(obj: Any) -> "PermissionResult":
+ def from_dict(obj: Any) -> "PermissionRequestWrite":
assert isinstance(obj, dict)
- kind = parse_enum(PermissionResultKind, obj.get("kind"))
- approval = from_union([from_none, UserToolSessionApproval.from_dict], obj.get("approval"))
- feedback = from_union([from_none, from_str], obj.get("feedback"))
- force_reject = from_union([from_none, from_bool], obj.get("forceReject"))
- interrupt = from_union([from_none, from_bool], obj.get("interrupt"))
- location_key = from_union([from_none, from_str], obj.get("locationKey"))
- message = from_union([from_none, from_str], obj.get("message"))
- path = from_union([from_none, from_str], obj.get("path"))
- reason = from_union([from_none, from_str], obj.get("reason"))
- rules = from_union([from_none, lambda x: from_list(PermissionRule.from_dict, x)], obj.get("rules"))
- return PermissionResult(
- kind=kind,
- approval=approval,
- feedback=feedback,
- force_reject=force_reject,
- interrupt=interrupt,
- location_key=location_key,
- message=message,
- path=path,
- reason=reason,
- rules=rules,
+ can_offer_session_approval = from_bool(obj.get("canOfferSessionApproval"))
+ diff = from_str(obj.get("diff"))
+ file_name = from_str(obj.get("fileName"))
+ intention = from_str(obj.get("intention"))
+ new_file_contents = from_union([from_none, from_str], obj.get("newFileContents"))
+ tool_call_id = from_union([from_none, from_str], obj.get("toolCallId"))
+ return PermissionRequestWrite(
+ can_offer_session_approval=can_offer_session_approval,
+ diff=diff,
+ file_name=file_name,
+ intention=intention,
+ new_file_contents=new_file_contents,
+ tool_call_id=tool_call_id,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["canOfferSessionApproval"] = from_bool(self.can_offer_session_approval)
+ result["diff"] = from_str(self.diff)
+ result["fileName"] = from_str(self.file_name)
+ result["intention"] = from_str(self.intention)
+ result["kind"] = self.kind
+ if self.new_file_contents is not None:
+ result["newFileContents"] = from_union([from_none, from_str], self.new_file_contents)
+ if self.tool_call_id is not None:
+ result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id)
+ return result
+
+
+@dataclass
+class PermissionRequestedData:
+ "Permission request notification requiring client approval with request details"
+ permission_request: PermissionRequest
+ request_id: str
+ prompt_request: PermissionPromptRequest | None = None
+ resolved_by_hook: bool | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "PermissionRequestedData":
+ assert isinstance(obj, dict)
+ permission_request = _load_PermissionRequest(obj.get("permissionRequest"))
+ request_id = from_str(obj.get("requestId"))
+ prompt_request = from_union([from_none, _load_PermissionPromptRequest], obj.get("promptRequest"))
+ resolved_by_hook = from_union([from_none, from_bool], obj.get("resolvedByHook"))
+ return PermissionRequestedData(
+ permission_request=permission_request,
+ request_id=request_id,
+ prompt_request=prompt_request,
+ resolved_by_hook=resolved_by_hook,
)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(PermissionResultKind, self.kind)
- if self.approval is not None:
- result["approval"] = from_union([from_none, lambda x: to_class(UserToolSessionApproval, x)], self.approval)
- if self.feedback is not None:
- result["feedback"] = from_union([from_none, from_str], self.feedback)
- if self.force_reject is not None:
- result["forceReject"] = from_union([from_none, from_bool], self.force_reject)
- if self.interrupt is not None:
- result["interrupt"] = from_union([from_none, from_bool], self.interrupt)
- if self.location_key is not None:
- result["locationKey"] = from_union([from_none, from_str], self.location_key)
- if self.message is not None:
- result["message"] = from_union([from_none, from_str], self.message)
- if self.path is not None:
- result["path"] = from_union([from_none, from_str], self.path)
- if self.reason is not None:
- result["reason"] = from_union([from_none, from_str], self.reason)
- if self.rules is not None:
- result["rules"] = from_union([from_none, lambda x: from_list(lambda x: to_class(PermissionRule, x), x)], self.rules)
+ result["permissionRequest"] = self.permission_request.to_dict()
+ result["requestId"] = from_str(self.request_id)
+ if self.prompt_request is not None:
+ result["promptRequest"] = from_union([from_none, lambda x: x.to_dict()], self.prompt_request)
+ if self.resolved_by_hook is not None:
+ result["resolvedByHook"] = from_union([from_none, from_bool], self.resolved_by_hook)
return result
@@ -3962,91 +4544,71 @@ def to_dict(self) -> dict:
@dataclass
-class SystemNotification:
- "Structured metadata identifying what triggered this notification"
- type: SystemNotificationType
- agent_id: str | None = None
- agent_type: str | None = None
+class SystemNotificationAgentCompleted:
+ "Schema for the `SystemNotificationAgentCompleted` type."
+ agent_id: str
+ agent_type: str
+ status: SystemNotificationAgentCompletedStatus
+ type: ClassVar[str] = "agent_completed"
description: str | None = None
- entry_id: str | None = None
- exit_code: int | None = None
prompt: str | None = None
- sender_name: str | None = None
- sender_type: str | None = None
- shell_id: str | None = None
- source_path: str | None = None
- status: SystemNotificationAgentCompletedStatus | None = None
- summary: str | None = None
- trigger_file: str | None = None
- trigger_tool: str | None = None
@staticmethod
- def from_dict(obj: Any) -> "SystemNotification":
+ def from_dict(obj: Any) -> "SystemNotificationAgentCompleted":
assert isinstance(obj, dict)
- type = parse_enum(SystemNotificationType, obj.get("type"))
- agent_id = from_union([from_none, from_str], obj.get("agentId"))
- agent_type = from_union([from_none, from_str], obj.get("agentType"))
+ agent_id = from_str(obj.get("agentId"))
+ agent_type = from_str(obj.get("agentType"))
+ status = parse_enum(SystemNotificationAgentCompletedStatus, obj.get("status"))
description = from_union([from_none, from_str], obj.get("description"))
- entry_id = from_union([from_none, from_str], obj.get("entryId"))
- exit_code = from_union([from_none, from_int], obj.get("exitCode"))
prompt = from_union([from_none, from_str], obj.get("prompt"))
- sender_name = from_union([from_none, from_str], obj.get("senderName"))
- sender_type = from_union([from_none, from_str], obj.get("senderType"))
- shell_id = from_union([from_none, from_str], obj.get("shellId"))
- source_path = from_union([from_none, from_str], obj.get("sourcePath"))
- status = from_union([from_none, lambda x: parse_enum(SystemNotificationAgentCompletedStatus, x)], obj.get("status"))
- summary = from_union([from_none, from_str], obj.get("summary"))
- trigger_file = from_union([from_none, from_str], obj.get("triggerFile"))
- trigger_tool = from_union([from_none, from_str], obj.get("triggerTool"))
- return SystemNotification(
- type=type,
+ return SystemNotificationAgentCompleted(
agent_id=agent_id,
agent_type=agent_type,
+ status=status,
description=description,
- entry_id=entry_id,
- exit_code=exit_code,
prompt=prompt,
- sender_name=sender_name,
- sender_type=sender_type,
- shell_id=shell_id,
- source_path=source_path,
- status=status,
- summary=summary,
- trigger_file=trigger_file,
- trigger_tool=trigger_tool,
)
def to_dict(self) -> dict:
result: dict = {}
- result["type"] = to_enum(SystemNotificationType, self.type)
- if self.agent_id is not None:
- result["agentId"] = from_union([from_none, from_str], self.agent_id)
- if self.agent_type is not None:
- result["agentType"] = from_union([from_none, from_str], self.agent_type)
+ result["agentId"] = from_str(self.agent_id)
+ result["agentType"] = from_str(self.agent_type)
+ result["status"] = to_enum(SystemNotificationAgentCompletedStatus, self.status)
+ result["type"] = self.type
if self.description is not None:
result["description"] = from_union([from_none, from_str], self.description)
- if self.entry_id is not None:
- result["entryId"] = from_union([from_none, from_str], self.entry_id)
- if self.exit_code is not None:
- result["exitCode"] = from_union([from_none, to_int], self.exit_code)
if self.prompt is not None:
result["prompt"] = from_union([from_none, from_str], self.prompt)
- if self.sender_name is not None:
- result["senderName"] = from_union([from_none, from_str], self.sender_name)
- if self.sender_type is not None:
- result["senderType"] = from_union([from_none, from_str], self.sender_type)
- if self.shell_id is not None:
- result["shellId"] = from_union([from_none, from_str], self.shell_id)
- if self.source_path is not None:
- result["sourcePath"] = from_union([from_none, from_str], self.source_path)
- if self.status is not None:
- result["status"] = from_union([from_none, lambda x: to_enum(SystemNotificationAgentCompletedStatus, x)], self.status)
- if self.summary is not None:
- result["summary"] = from_union([from_none, from_str], self.summary)
- if self.trigger_file is not None:
- result["triggerFile"] = from_union([from_none, from_str], self.trigger_file)
- if self.trigger_tool is not None:
- result["triggerTool"] = from_union([from_none, from_str], self.trigger_tool)
+ return result
+
+
+@dataclass
+class SystemNotificationAgentIdle:
+ "Schema for the `SystemNotificationAgentIdle` type."
+ agent_id: str
+ agent_type: str
+ type: ClassVar[str] = "agent_idle"
+ description: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "SystemNotificationAgentIdle":
+ assert isinstance(obj, dict)
+ agent_id = from_str(obj.get("agentId"))
+ agent_type = from_str(obj.get("agentType"))
+ description = from_union([from_none, from_str], obj.get("description"))
+ return SystemNotificationAgentIdle(
+ agent_id=agent_id,
+ agent_type=agent_type,
+ description=description,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["agentId"] = from_str(self.agent_id)
+ result["agentType"] = from_str(self.agent_type)
+ result["type"] = self.type
+ if self.description is not None:
+ result["description"] = from_union([from_none, from_str], self.description)
return result
@@ -4060,7 +4622,7 @@ class SystemNotificationData:
def from_dict(obj: Any) -> "SystemNotificationData":
assert isinstance(obj, dict)
content = from_str(obj.get("content"))
- kind = SystemNotification.from_dict(obj.get("kind"))
+ kind = _load_SystemNotification(obj.get("kind"))
return SystemNotificationData(
content=content,
kind=kind,
@@ -4069,86 +4631,252 @@ def from_dict(obj: Any) -> "SystemNotificationData":
def to_dict(self) -> dict:
result: dict = {}
result["content"] = from_str(self.content)
- result["kind"] = to_class(SystemNotification, self.kind)
+ result["kind"] = self.kind.to_dict()
return result
@dataclass
-class ToolExecutionCompleteContent:
- "A content block within a tool result, which may be text, terminal output, image, audio, or a resource"
- type: ToolExecutionCompleteContentType
- cwd: str | None = None
- data: str | None = None
+class SystemNotificationInstructionDiscovered:
+ "Schema for the `SystemNotificationInstructionDiscovered` type."
+ source_path: str
+ trigger_file: str
+ trigger_tool: str
+ type: ClassVar[str] = "instruction_discovered"
+ description: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "SystemNotificationInstructionDiscovered":
+ assert isinstance(obj, dict)
+ source_path = from_str(obj.get("sourcePath"))
+ trigger_file = from_str(obj.get("triggerFile"))
+ trigger_tool = from_str(obj.get("triggerTool"))
+ description = from_union([from_none, from_str], obj.get("description"))
+ return SystemNotificationInstructionDiscovered(
+ source_path=source_path,
+ trigger_file=trigger_file,
+ trigger_tool=trigger_tool,
+ description=description,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["sourcePath"] = from_str(self.source_path)
+ result["triggerFile"] = from_str(self.trigger_file)
+ result["triggerTool"] = from_str(self.trigger_tool)
+ result["type"] = self.type
+ if self.description is not None:
+ result["description"] = from_union([from_none, from_str], self.description)
+ return result
+
+
+@dataclass
+class SystemNotificationNewInboxMessage:
+ "Schema for the `SystemNotificationNewInboxMessage` type."
+ entry_id: str
+ sender_name: str
+ sender_type: str
+ summary: str
+ type: ClassVar[str] = "new_inbox_message"
+
+ @staticmethod
+ def from_dict(obj: Any) -> "SystemNotificationNewInboxMessage":
+ assert isinstance(obj, dict)
+ entry_id = from_str(obj.get("entryId"))
+ sender_name = from_str(obj.get("senderName"))
+ sender_type = from_str(obj.get("senderType"))
+ summary = from_str(obj.get("summary"))
+ return SystemNotificationNewInboxMessage(
+ entry_id=entry_id,
+ sender_name=sender_name,
+ sender_type=sender_type,
+ summary=summary,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["entryId"] = from_str(self.entry_id)
+ result["senderName"] = from_str(self.sender_name)
+ result["senderType"] = from_str(self.sender_type)
+ result["summary"] = from_str(self.summary)
+ result["type"] = self.type
+ return result
+
+
+@dataclass
+class SystemNotificationShellCompleted:
+ "Schema for the `SystemNotificationShellCompleted` type."
+ shell_id: str
+ type: ClassVar[str] = "shell_completed"
description: str | None = None
exit_code: int | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "SystemNotificationShellCompleted":
+ assert isinstance(obj, dict)
+ shell_id = from_str(obj.get("shellId"))
+ description = from_union([from_none, from_str], obj.get("description"))
+ exit_code = from_union([from_none, from_int], obj.get("exitCode"))
+ return SystemNotificationShellCompleted(
+ shell_id=shell_id,
+ description=description,
+ exit_code=exit_code,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["shellId"] = from_str(self.shell_id)
+ result["type"] = self.type
+ if self.description is not None:
+ result["description"] = from_union([from_none, from_str], self.description)
+ if self.exit_code is not None:
+ result["exitCode"] = from_union([from_none, to_int], self.exit_code)
+ return result
+
+
+@dataclass
+class SystemNotificationShellDetachedCompleted:
+ "Schema for the `SystemNotificationShellDetachedCompleted` type."
+ shell_id: str
+ type: ClassVar[str] = "shell_detached_completed"
+ description: str | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "SystemNotificationShellDetachedCompleted":
+ assert isinstance(obj, dict)
+ shell_id = from_str(obj.get("shellId"))
+ description = from_union([from_none, from_str], obj.get("description"))
+ return SystemNotificationShellDetachedCompleted(
+ shell_id=shell_id,
+ description=description,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["shellId"] = from_str(self.shell_id)
+ result["type"] = self.type
+ if self.description is not None:
+ result["description"] = from_union([from_none, from_str], self.description)
+ return result
+
+
+@dataclass
+class ToolExecutionCompleteContentAudio:
+ "Audio content block with base64-encoded data"
+ data: str
+ mime_type: str
+ type: ClassVar[str] = "audio"
+
+ @staticmethod
+ def from_dict(obj: Any) -> "ToolExecutionCompleteContentAudio":
+ assert isinstance(obj, dict)
+ data = from_str(obj.get("data"))
+ mime_type = from_str(obj.get("mimeType"))
+ return ToolExecutionCompleteContentAudio(
+ data=data,
+ mime_type=mime_type,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["data"] = from_str(self.data)
+ result["mimeType"] = from_str(self.mime_type)
+ result["type"] = self.type
+ return result
+
+
+@dataclass
+class ToolExecutionCompleteContentImage:
+ "Image content block with base64-encoded data"
+ data: str
+ mime_type: str
+ type: ClassVar[str] = "image"
+
+ @staticmethod
+ def from_dict(obj: Any) -> "ToolExecutionCompleteContentImage":
+ assert isinstance(obj, dict)
+ data = from_str(obj.get("data"))
+ mime_type = from_str(obj.get("mimeType"))
+ return ToolExecutionCompleteContentImage(
+ data=data,
+ mime_type=mime_type,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["data"] = from_str(self.data)
+ result["mimeType"] = from_str(self.mime_type)
+ result["type"] = self.type
+ return result
+
+
+@dataclass
+class ToolExecutionCompleteContentResource:
+ "Embedded resource content block with inline text or binary data"
+ resource: ToolExecutionCompleteContentResourceDetails
+ type: ClassVar[str] = "resource"
+
+ @staticmethod
+ def from_dict(obj: Any) -> "ToolExecutionCompleteContentResource":
+ assert isinstance(obj, dict)
+ resource = from_union([EmbeddedTextResourceContents.from_dict, EmbeddedBlobResourceContents.from_dict], obj.get("resource"))
+ return ToolExecutionCompleteContentResource(
+ resource=resource,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["resource"] = from_union([lambda x: to_class(EmbeddedTextResourceContents, x), lambda x: to_class(EmbeddedBlobResourceContents, x)], self.resource)
+ result["type"] = self.type
+ return result
+
+
+@dataclass
+class ToolExecutionCompleteContentResourceLink:
+ "Resource link content block referencing an external resource"
+ name: str
+ type: ClassVar[str] = "resource_link"
+ uri: str
+ description: str | None = None
icons: list[ToolExecutionCompleteContentResourceLinkIcon] | None = None
mime_type: str | None = None
- name: str | None = None
- resource: ToolExecutionCompleteContentResourceDetails | None = None
size: int | None = None
- text: str | None = None
title: str | None = None
- uri: str | None = None
@staticmethod
- def from_dict(obj: Any) -> "ToolExecutionCompleteContent":
+ def from_dict(obj: Any) -> "ToolExecutionCompleteContentResourceLink":
assert isinstance(obj, dict)
- type = parse_enum(ToolExecutionCompleteContentType, obj.get("type"))
- cwd = from_union([from_none, from_str], obj.get("cwd"))
- data = from_union([from_none, from_str], obj.get("data"))
+ name = from_str(obj.get("name"))
+ uri = from_str(obj.get("uri"))
description = from_union([from_none, from_str], obj.get("description"))
- exit_code = from_union([from_none, from_int], obj.get("exitCode"))
icons = from_union([from_none, lambda x: from_list(ToolExecutionCompleteContentResourceLinkIcon.from_dict, x)], obj.get("icons"))
mime_type = from_union([from_none, from_str], obj.get("mimeType"))
- name = from_union([from_none, from_str], obj.get("name"))
- resource = from_union([from_none, lambda x: from_union([EmbeddedTextResourceContents.from_dict, EmbeddedBlobResourceContents.from_dict], x)], obj.get("resource"))
size = from_union([from_none, from_int], obj.get("size"))
- text = from_union([from_none, from_str], obj.get("text"))
title = from_union([from_none, from_str], obj.get("title"))
- uri = from_union([from_none, from_str], obj.get("uri"))
- return ToolExecutionCompleteContent(
- type=type,
- cwd=cwd,
- data=data,
+ return ToolExecutionCompleteContentResourceLink(
+ name=name,
+ uri=uri,
description=description,
- exit_code=exit_code,
icons=icons,
mime_type=mime_type,
- name=name,
- resource=resource,
size=size,
- text=text,
title=title,
- uri=uri,
)
def to_dict(self) -> dict:
result: dict = {}
- result["type"] = to_enum(ToolExecutionCompleteContentType, self.type)
- if self.cwd is not None:
- result["cwd"] = from_union([from_none, from_str], self.cwd)
- if self.data is not None:
- result["data"] = from_union([from_none, from_str], self.data)
+ result["name"] = from_str(self.name)
+ result["type"] = self.type
+ result["uri"] = from_str(self.uri)
if self.description is not None:
result["description"] = from_union([from_none, from_str], self.description)
- if self.exit_code is not None:
- result["exitCode"] = from_union([from_none, to_int], self.exit_code)
if self.icons is not None:
result["icons"] = from_union([from_none, lambda x: from_list(lambda x: to_class(ToolExecutionCompleteContentResourceLinkIcon, x), x)], self.icons)
if self.mime_type is not None:
result["mimeType"] = from_union([from_none, from_str], self.mime_type)
- if self.name is not None:
- result["name"] = from_union([from_none, from_str], self.name)
- if self.resource is not None:
- result["resource"] = from_union([from_none, lambda x: from_union([lambda x: to_class(EmbeddedTextResourceContents, x), lambda x: to_class(EmbeddedBlobResourceContents, x)], x)], self.resource)
if self.size is not None:
result["size"] = from_union([from_none, to_int], self.size)
- if self.text is not None:
- result["text"] = from_union([from_none, from_str], self.text)
if self.title is not None:
result["title"] = from_union([from_none, from_str], self.title)
- if self.uri is not None:
- result["uri"] = from_union([from_none, from_str], self.uri)
return result
@@ -4176,13 +4904,65 @@ def from_dict(obj: Any) -> "ToolExecutionCompleteContentResourceLinkIcon":
def to_dict(self) -> dict:
result: dict = {}
- result["src"] = from_str(self.src)
- if self.mime_type is not None:
- result["mimeType"] = from_union([from_none, from_str], self.mime_type)
- if self.sizes is not None:
- result["sizes"] = from_union([from_none, lambda x: from_list(from_str, x)], self.sizes)
- if self.theme is not None:
- result["theme"] = from_union([from_none, lambda x: to_enum(ToolExecutionCompleteContentResourceLinkIconTheme, x)], self.theme)
+ result["src"] = from_str(self.src)
+ if self.mime_type is not None:
+ result["mimeType"] = from_union([from_none, from_str], self.mime_type)
+ if self.sizes is not None:
+ result["sizes"] = from_union([from_none, lambda x: from_list(from_str, x)], self.sizes)
+ if self.theme is not None:
+ result["theme"] = from_union([from_none, lambda x: to_enum(ToolExecutionCompleteContentResourceLinkIconTheme, x)], self.theme)
+ return result
+
+
+@dataclass
+class ToolExecutionCompleteContentTerminal:
+ "Terminal/shell output content block with optional exit code and working directory"
+ text: str
+ type: ClassVar[str] = "terminal"
+ cwd: str | None = None
+ exit_code: int | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "ToolExecutionCompleteContentTerminal":
+ assert isinstance(obj, dict)
+ text = from_str(obj.get("text"))
+ cwd = from_union([from_none, from_str], obj.get("cwd"))
+ exit_code = from_union([from_none, from_int], obj.get("exitCode"))
+ return ToolExecutionCompleteContentTerminal(
+ text=text,
+ cwd=cwd,
+ exit_code=exit_code,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["text"] = from_str(self.text)
+ result["type"] = self.type
+ if self.cwd is not None:
+ result["cwd"] = from_union([from_none, from_str], self.cwd)
+ if self.exit_code is not None:
+ result["exitCode"] = from_union([from_none, to_int], self.exit_code)
+ return result
+
+
+@dataclass
+class ToolExecutionCompleteContentText:
+ "Plain text content block"
+ text: str
+ type: ClassVar[str] = "text"
+
+ @staticmethod
+ def from_dict(obj: Any) -> "ToolExecutionCompleteContentText":
+ assert isinstance(obj, dict)
+ text = from_str(obj.get("text"))
+ return ToolExecutionCompleteContentText(
+ text=text,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["text"] = from_str(self.text)
+ result["type"] = self.type
return result
@@ -4290,7 +5070,7 @@ class ToolExecutionCompleteResult:
def from_dict(obj: Any) -> "ToolExecutionCompleteResult":
assert isinstance(obj, dict)
content = from_str(obj.get("content"))
- contents = from_union([from_none, lambda x: from_list(ToolExecutionCompleteContent.from_dict, x)], obj.get("contents"))
+ contents = from_union([from_none, lambda x: from_list(_load_ToolExecutionCompleteContent, x)], obj.get("contents"))
detailed_content = from_union([from_none, from_str], obj.get("detailedContent"))
return ToolExecutionCompleteResult(
content=content,
@@ -4302,7 +5082,7 @@ def to_dict(self) -> dict:
result: dict = {}
result["content"] = from_str(self.content)
if self.contents is not None:
- result["contents"] = from_union([from_none, lambda x: from_list(lambda x: to_class(ToolExecutionCompleteContent, x), x)], self.contents)
+ result["contents"] = from_union([from_none, lambda x: from_list(lambda x: x.to_dict(), x)], self.contents)
if self.detailed_content is not None:
result["detailedContent"] = from_union([from_none, from_str], self.detailed_content)
return result
@@ -4499,86 +5279,87 @@ def to_dict(self) -> dict:
@dataclass
-class UserMessageAttachment:
- "A user message attachment — a file, directory, code selection, blob, or GitHub reference"
- type: UserMessageAttachmentType
- data: str | None = None
+class UserMessageAttachmentBlob:
+ "Blob attachment with inline base64-encoded data"
+ data: str
+ mime_type: str
+ type: ClassVar[str] = "blob"
display_name: str | None = None
- file_path: str | None = None
- line_range: UserMessageAttachmentFileLineRange | None = None
- mime_type: str | None = None
- number: int | None = None
- path: str | None = None
- reference_type: UserMessageAttachmentGithubReferenceType | None = None
- selection: UserMessageAttachmentSelectionDetails | None = None
- state: str | None = None
- text: str | None = None
- title: str | None = None
- url: str | None = None
@staticmethod
- def from_dict(obj: Any) -> "UserMessageAttachment":
+ def from_dict(obj: Any) -> "UserMessageAttachmentBlob":
assert isinstance(obj, dict)
- type = parse_enum(UserMessageAttachmentType, obj.get("type"))
- data = from_union([from_none, from_str], obj.get("data"))
+ data = from_str(obj.get("data"))
+ mime_type = from_str(obj.get("mimeType"))
display_name = from_union([from_none, from_str], obj.get("displayName"))
- file_path = from_union([from_none, from_str], obj.get("filePath"))
- line_range = from_union([from_none, UserMessageAttachmentFileLineRange.from_dict], obj.get("lineRange"))
- mime_type = from_union([from_none, from_str], obj.get("mimeType"))
- number = from_union([from_none, from_int], obj.get("number"))
- path = from_union([from_none, from_str], obj.get("path"))
- reference_type = from_union([from_none, lambda x: parse_enum(UserMessageAttachmentGithubReferenceType, x)], obj.get("referenceType"))
- selection = from_union([from_none, UserMessageAttachmentSelectionDetails.from_dict], obj.get("selection"))
- state = from_union([from_none, from_str], obj.get("state"))
- text = from_union([from_none, from_str], obj.get("text"))
- title = from_union([from_none, from_str], obj.get("title"))
- url = from_union([from_none, from_str], obj.get("url"))
- return UserMessageAttachment(
- type=type,
+ return UserMessageAttachmentBlob(
data=data,
- display_name=display_name,
- file_path=file_path,
- line_range=line_range,
mime_type=mime_type,
- number=number,
- path=path,
- reference_type=reference_type,
- selection=selection,
- state=state,
- text=text,
- title=title,
- url=url,
+ display_name=display_name,
)
def to_dict(self) -> dict:
result: dict = {}
- result["type"] = to_enum(UserMessageAttachmentType, self.type)
- if self.data is not None:
- result["data"] = from_union([from_none, from_str], self.data)
+ result["data"] = from_str(self.data)
+ result["mimeType"] = from_str(self.mime_type)
+ result["type"] = self.type
if self.display_name is not None:
result["displayName"] = from_union([from_none, from_str], self.display_name)
- if self.file_path is not None:
- result["filePath"] = from_union([from_none, from_str], self.file_path)
+ return result
+
+
+@dataclass
+class UserMessageAttachmentDirectory:
+ "Directory attachment"
+ display_name: str
+ path: str
+ type: ClassVar[str] = "directory"
+
+ @staticmethod
+ def from_dict(obj: Any) -> "UserMessageAttachmentDirectory":
+ assert isinstance(obj, dict)
+ display_name = from_str(obj.get("displayName"))
+ path = from_str(obj.get("path"))
+ return UserMessageAttachmentDirectory(
+ display_name=display_name,
+ path=path,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["displayName"] = from_str(self.display_name)
+ result["path"] = from_str(self.path)
+ result["type"] = self.type
+ return result
+
+
+@dataclass
+class UserMessageAttachmentFile:
+ "File attachment"
+ display_name: str
+ path: str
+ type: ClassVar[str] = "file"
+ line_range: UserMessageAttachmentFileLineRange | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "UserMessageAttachmentFile":
+ assert isinstance(obj, dict)
+ display_name = from_str(obj.get("displayName"))
+ path = from_str(obj.get("path"))
+ line_range = from_union([from_none, UserMessageAttachmentFileLineRange.from_dict], obj.get("lineRange"))
+ return UserMessageAttachmentFile(
+ display_name=display_name,
+ path=path,
+ line_range=line_range,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["displayName"] = from_str(self.display_name)
+ result["path"] = from_str(self.path)
+ result["type"] = self.type
if self.line_range is not None:
result["lineRange"] = from_union([from_none, lambda x: to_class(UserMessageAttachmentFileLineRange, x)], self.line_range)
- if self.mime_type is not None:
- result["mimeType"] = from_union([from_none, from_str], self.mime_type)
- if self.number is not None:
- result["number"] = from_union([from_none, to_int], self.number)
- if self.path is not None:
- result["path"] = from_union([from_none, from_str], self.path)
- if self.reference_type is not None:
- result["referenceType"] = from_union([from_none, lambda x: to_enum(UserMessageAttachmentGithubReferenceType, x)], self.reference_type)
- if self.selection is not None:
- result["selection"] = from_union([from_none, lambda x: to_class(UserMessageAttachmentSelectionDetails, x)], self.selection)
- if self.state is not None:
- result["state"] = from_union([from_none, from_str], self.state)
- if self.text is not None:
- result["text"] = from_union([from_none, from_str], self.text)
- if self.title is not None:
- result["title"] = from_union([from_none, from_str], self.title)
- if self.url is not None:
- result["url"] = from_union([from_none, from_str], self.url)
return result
@@ -4605,6 +5386,76 @@ def to_dict(self) -> dict:
return result
+@dataclass
+class UserMessageAttachmentGithubReference:
+ "GitHub issue, pull request, or discussion reference"
+ number: int
+ reference_type: UserMessageAttachmentGithubReferenceType
+ state: str
+ title: str
+ type: ClassVar[str] = "github_reference"
+ url: str
+
+ @staticmethod
+ def from_dict(obj: Any) -> "UserMessageAttachmentGithubReference":
+ assert isinstance(obj, dict)
+ number = from_int(obj.get("number"))
+ reference_type = parse_enum(UserMessageAttachmentGithubReferenceType, obj.get("referenceType"))
+ state = from_str(obj.get("state"))
+ title = from_str(obj.get("title"))
+ url = from_str(obj.get("url"))
+ return UserMessageAttachmentGithubReference(
+ number=number,
+ reference_type=reference_type,
+ state=state,
+ title=title,
+ url=url,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["number"] = to_int(self.number)
+ result["referenceType"] = to_enum(UserMessageAttachmentGithubReferenceType, self.reference_type)
+ result["state"] = from_str(self.state)
+ result["title"] = from_str(self.title)
+ result["type"] = self.type
+ result["url"] = from_str(self.url)
+ return result
+
+
+@dataclass
+class UserMessageAttachmentSelection:
+ "Code selection attachment from an editor"
+ display_name: str
+ file_path: str
+ selection: UserMessageAttachmentSelectionDetails
+ text: str
+ type: ClassVar[str] = "selection"
+
+ @staticmethod
+ def from_dict(obj: Any) -> "UserMessageAttachmentSelection":
+ assert isinstance(obj, dict)
+ display_name = from_str(obj.get("displayName"))
+ file_path = from_str(obj.get("filePath"))
+ selection = UserMessageAttachmentSelectionDetails.from_dict(obj.get("selection"))
+ text = from_str(obj.get("text"))
+ return UserMessageAttachmentSelection(
+ display_name=display_name,
+ file_path=file_path,
+ selection=selection,
+ text=text,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["displayName"] = from_str(self.display_name)
+ result["filePath"] = from_str(self.file_path)
+ result["selection"] = to_class(UserMessageAttachmentSelectionDetails, self.selection)
+ result["text"] = from_str(self.text)
+ result["type"] = self.type
+ return result
+
+
@dataclass
class UserMessageAttachmentSelectionDetails:
"Position range of the selection within the file"
@@ -4693,7 +5544,7 @@ def from_dict(obj: Any) -> "UserMessageData":
assert isinstance(obj, dict)
content = from_str(obj.get("content"))
agent_mode = from_union([from_none, lambda x: parse_enum(UserMessageAgentMode, x)], obj.get("agentMode"))
- attachments = from_union([from_none, lambda x: from_list(UserMessageAttachment.from_dict, x)], obj.get("attachments"))
+ attachments = from_union([from_none, lambda x: from_list(_load_UserMessageAttachment, x)], obj.get("attachments"))
interaction_id = from_union([from_none, from_str], obj.get("interactionId"))
is_autopilot_continuation = from_union([from_none, from_bool], obj.get("isAutopilotContinuation"))
native_document_path_fallback_paths = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("nativeDocumentPathFallbackPaths"))
@@ -4720,7 +5571,7 @@ def to_dict(self) -> dict:
if self.agent_mode is not None:
result["agentMode"] = from_union([from_none, lambda x: to_enum(UserMessageAgentMode, x)], self.agent_mode)
if self.attachments is not None:
- result["attachments"] = from_union([from_none, lambda x: from_list(lambda x: to_class(UserMessageAttachment, x), x)], self.attachments)
+ result["attachments"] = from_union([from_none, lambda x: from_list(lambda x: x.to_dict(), x)], self.attachments)
if self.interaction_id is not None:
result["interactionId"] = from_union([from_none, from_str], self.interaction_id)
if self.is_autopilot_continuation is not None:
@@ -4739,46 +5590,163 @@ def to_dict(self) -> dict:
@dataclass
-class UserToolSessionApproval:
- "The approval to add as a session-scoped rule"
- kind: UserToolSessionApprovalKind
- command_identifiers: list[str] | None = None
- extension_name: str | None = None
+class UserToolSessionApprovalCommands:
+ "Schema for the `UserToolSessionApprovalCommands` type."
+ command_identifiers: list[str]
+ kind: ClassVar[str] = "commands"
+
+ @staticmethod
+ def from_dict(obj: Any) -> "UserToolSessionApprovalCommands":
+ assert isinstance(obj, dict)
+ command_identifiers = from_list(from_str, obj.get("commandIdentifiers"))
+ return UserToolSessionApprovalCommands(
+ command_identifiers=command_identifiers,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["commandIdentifiers"] = from_list(from_str, self.command_identifiers)
+ result["kind"] = self.kind
+ return result
+
+
+@dataclass
+class UserToolSessionApprovalCustomTool:
+ "Schema for the `UserToolSessionApprovalCustomTool` type."
+ kind: ClassVar[str] = "custom-tool"
+ tool_name: str
+
+ @staticmethod
+ def from_dict(obj: Any) -> "UserToolSessionApprovalCustomTool":
+ assert isinstance(obj, dict)
+ tool_name = from_str(obj.get("toolName"))
+ return UserToolSessionApprovalCustomTool(
+ tool_name=tool_name,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = self.kind
+ result["toolName"] = from_str(self.tool_name)
+ return result
+
+
+@dataclass
+class UserToolSessionApprovalExtensionManagement:
+ "Schema for the `UserToolSessionApprovalExtensionManagement` type."
+ kind: ClassVar[str] = "extension-management"
operation: str | None = None
- server_name: str | None = None
- tool_name: str | None = None
@staticmethod
- def from_dict(obj: Any) -> "UserToolSessionApproval":
+ def from_dict(obj: Any) -> "UserToolSessionApprovalExtensionManagement":
assert isinstance(obj, dict)
- kind = parse_enum(UserToolSessionApprovalKind, obj.get("kind"))
- command_identifiers = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("commandIdentifiers"))
- extension_name = from_union([from_none, from_str], obj.get("extensionName"))
operation = from_union([from_none, from_str], obj.get("operation"))
- server_name = from_union([from_none, from_str], obj.get("serverName"))
- tool_name = from_union([from_none, from_str], obj.get("toolName"))
- return UserToolSessionApproval(
- kind=kind,
- command_identifiers=command_identifiers,
- extension_name=extension_name,
+ return UserToolSessionApprovalExtensionManagement(
operation=operation,
- server_name=server_name,
- tool_name=tool_name,
)
def to_dict(self) -> dict:
result: dict = {}
- result["kind"] = to_enum(UserToolSessionApprovalKind, self.kind)
- if self.command_identifiers is not None:
- result["commandIdentifiers"] = from_union([from_none, lambda x: from_list(from_str, x)], self.command_identifiers)
- if self.extension_name is not None:
- result["extensionName"] = from_union([from_none, from_str], self.extension_name)
+ result["kind"] = self.kind
if self.operation is not None:
result["operation"] = from_union([from_none, from_str], self.operation)
- if self.server_name is not None:
- result["serverName"] = from_union([from_none, from_str], self.server_name)
- if self.tool_name is not None:
- result["toolName"] = from_union([from_none, from_str], self.tool_name)
+ return result
+
+
+@dataclass
+class UserToolSessionApprovalExtensionPermissionAccess:
+ "Schema for the `UserToolSessionApprovalExtensionPermissionAccess` type."
+ extension_name: str
+ kind: ClassVar[str] = "extension-permission-access"
+
+ @staticmethod
+ def from_dict(obj: Any) -> "UserToolSessionApprovalExtensionPermissionAccess":
+ assert isinstance(obj, dict)
+ extension_name = from_str(obj.get("extensionName"))
+ return UserToolSessionApprovalExtensionPermissionAccess(
+ extension_name=extension_name,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["extensionName"] = from_str(self.extension_name)
+ result["kind"] = self.kind
+ return result
+
+
+@dataclass
+class UserToolSessionApprovalMcp:
+ "Schema for the `UserToolSessionApprovalMcp` type."
+ kind: ClassVar[str] = "mcp"
+ server_name: str
+ tool_name: str | None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "UserToolSessionApprovalMcp":
+ assert isinstance(obj, dict)
+ server_name = from_str(obj.get("serverName"))
+ tool_name = from_union([from_none, from_str], obj.get("toolName"))
+ return UserToolSessionApprovalMcp(
+ server_name=server_name,
+ tool_name=tool_name,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = self.kind
+ result["serverName"] = from_str(self.server_name)
+ result["toolName"] = from_union([from_none, from_str], self.tool_name)
+ return result
+
+
+@dataclass
+class UserToolSessionApprovalMemory:
+ "Schema for the `UserToolSessionApprovalMemory` type."
+ kind: ClassVar[str] = "memory"
+
+ @staticmethod
+ def from_dict(obj: Any) -> "UserToolSessionApprovalMemory":
+ assert isinstance(obj, dict)
+ return UserToolSessionApprovalMemory(
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = self.kind
+ return result
+
+
+@dataclass
+class UserToolSessionApprovalRead:
+ "Schema for the `UserToolSessionApprovalRead` type."
+ kind: ClassVar[str] = "read"
+
+ @staticmethod
+ def from_dict(obj: Any) -> "UserToolSessionApprovalRead":
+ assert isinstance(obj, dict)
+ return UserToolSessionApprovalRead(
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = self.kind
+ return result
+
+
+@dataclass
+class UserToolSessionApprovalWrite:
+ "Schema for the `UserToolSessionApprovalWrite` type."
+ kind: ClassVar[str] = "write"
+
+ @staticmethod
+ def from_dict(obj: Any) -> "UserToolSessionApprovalWrite":
+ assert isinstance(obj, dict)
+ return UserToolSessionApprovalWrite(
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = self.kind
return result
@@ -4836,10 +5804,142 @@ def to_dict(self) -> dict:
return result
+def _load_PermissionPromptRequest(obj: Any) -> "PermissionPromptRequest":
+ assert isinstance(obj, dict)
+ kind = obj.get("kind")
+ match kind:
+ case "commands": return PermissionPromptRequestCommands.from_dict(obj)
+ case "write": return PermissionPromptRequestWrite.from_dict(obj)
+ case "read": return PermissionPromptRequestRead.from_dict(obj)
+ case "mcp": return PermissionPromptRequestMcp.from_dict(obj)
+ case "url": return PermissionPromptRequestUrl.from_dict(obj)
+ case "memory": return PermissionPromptRequestMemory.from_dict(obj)
+ case "custom-tool": return PermissionPromptRequestCustomTool.from_dict(obj)
+ case "path": return PermissionPromptRequestPath.from_dict(obj)
+ case "hook": return PermissionPromptRequestHook.from_dict(obj)
+ case "extension-management": return PermissionPromptRequestExtensionManagement.from_dict(obj)
+ case "extension-permission-access": return PermissionPromptRequestExtensionPermissionAccess.from_dict(obj)
+ case _: raise ValueError(f"Unknown PermissionPromptRequest kind: {kind!r}")
+
+
+def _load_PermissionRequest(obj: Any) -> "PermissionRequest":
+ assert isinstance(obj, dict)
+ kind = obj.get("kind")
+ match kind:
+ case "shell": return PermissionRequestShell.from_dict(obj)
+ case "write": return PermissionRequestWrite.from_dict(obj)
+ case "read": return PermissionRequestRead.from_dict(obj)
+ case "mcp": return PermissionRequestMcp.from_dict(obj)
+ case "url": return PermissionRequestUrl.from_dict(obj)
+ case "memory": return PermissionRequestMemory.from_dict(obj)
+ case "custom-tool": return PermissionRequestCustomTool.from_dict(obj)
+ case "hook": return PermissionRequestHook.from_dict(obj)
+ case "extension-management": return PermissionRequestExtensionManagement.from_dict(obj)
+ case "extension-permission-access": return PermissionRequestExtensionPermissionAccess.from_dict(obj)
+ case _: raise ValueError(f"Unknown PermissionRequest kind: {kind!r}")
+
+
+def _load_PermissionResult(obj: Any) -> "PermissionResult":
+ assert isinstance(obj, dict)
+ kind = obj.get("kind")
+ match kind:
+ case "approved": return PermissionApproved.from_dict(obj)
+ case "approved-for-session": return PermissionApprovedForSession.from_dict(obj)
+ case "approved-for-location": return PermissionApprovedForLocation.from_dict(obj)
+ case "cancelled": return PermissionCancelled.from_dict(obj)
+ case "denied-by-rules": return PermissionDeniedByRules.from_dict(obj)
+ case "denied-no-approval-rule-and-could-not-request-from-user": return PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser.from_dict(obj)
+ case "denied-interactively-by-user": return PermissionDeniedInteractivelyByUser.from_dict(obj)
+ case "denied-by-content-exclusion-policy": return PermissionDeniedByContentExclusionPolicy.from_dict(obj)
+ case "denied-by-permission-request-hook": return PermissionDeniedByPermissionRequestHook.from_dict(obj)
+ case _: raise ValueError(f"Unknown PermissionResult kind: {kind!r}")
+
+
+def _load_SystemNotification(obj: Any) -> "SystemNotification":
+ assert isinstance(obj, dict)
+ kind = obj.get("type")
+ match kind:
+ case "agent_completed": return SystemNotificationAgentCompleted.from_dict(obj)
+ case "agent_idle": return SystemNotificationAgentIdle.from_dict(obj)
+ case "new_inbox_message": return SystemNotificationNewInboxMessage.from_dict(obj)
+ case "shell_completed": return SystemNotificationShellCompleted.from_dict(obj)
+ case "shell_detached_completed": return SystemNotificationShellDetachedCompleted.from_dict(obj)
+ case "instruction_discovered": return SystemNotificationInstructionDiscovered.from_dict(obj)
+ case _: raise ValueError(f"Unknown SystemNotification type: {kind!r}")
+
+
+def _load_ToolExecutionCompleteContent(obj: Any) -> "ToolExecutionCompleteContent":
+ assert isinstance(obj, dict)
+ kind = obj.get("type")
+ match kind:
+ case "text": return ToolExecutionCompleteContentText.from_dict(obj)
+ case "terminal": return ToolExecutionCompleteContentTerminal.from_dict(obj)
+ case "image": return ToolExecutionCompleteContentImage.from_dict(obj)
+ case "audio": return ToolExecutionCompleteContentAudio.from_dict(obj)
+ case "resource_link": return ToolExecutionCompleteContentResourceLink.from_dict(obj)
+ case "resource": return ToolExecutionCompleteContentResource.from_dict(obj)
+ case _: raise ValueError(f"Unknown ToolExecutionCompleteContent type: {kind!r}")
+
+
+def _load_UserMessageAttachment(obj: Any) -> "UserMessageAttachment":
+ assert isinstance(obj, dict)
+ kind = obj.get("type")
+ match kind:
+ case "file": return UserMessageAttachmentFile.from_dict(obj)
+ case "directory": return UserMessageAttachmentDirectory.from_dict(obj)
+ case "selection": return UserMessageAttachmentSelection.from_dict(obj)
+ case "github_reference": return UserMessageAttachmentGithubReference.from_dict(obj)
+ case "blob": return UserMessageAttachmentBlob.from_dict(obj)
+ case _: raise ValueError(f"Unknown UserMessageAttachment type: {kind!r}")
+
+
+def _load_UserToolSessionApproval(obj: Any) -> "UserToolSessionApproval":
+ assert isinstance(obj, dict)
+ kind = obj.get("kind")
+ match kind:
+ case "commands": return UserToolSessionApprovalCommands.from_dict(obj)
+ case "read": return UserToolSessionApprovalRead.from_dict(obj)
+ case "write": return UserToolSessionApprovalWrite.from_dict(obj)
+ case "mcp": return UserToolSessionApprovalMcp.from_dict(obj)
+ case "memory": return UserToolSessionApprovalMemory.from_dict(obj)
+ case "custom-tool": return UserToolSessionApprovalCustomTool.from_dict(obj)
+ case "extension-management": return UserToolSessionApprovalExtensionManagement.from_dict(obj)
+ case "extension-permission-access": return UserToolSessionApprovalExtensionPermissionAccess.from_dict(obj)
+ case _: raise ValueError(f"Unknown UserToolSessionApproval kind: {kind!r}")
+
+
+# A content block within a tool result, which may be text, terminal output, image, audio, or a resource
+ToolExecutionCompleteContent = ToolExecutionCompleteContentText | ToolExecutionCompleteContentTerminal | ToolExecutionCompleteContentImage | ToolExecutionCompleteContentAudio | ToolExecutionCompleteContentResourceLink | ToolExecutionCompleteContentResource
+
+
+# A user message attachment — a file, directory, code selection, blob, or GitHub reference
+UserMessageAttachment = UserMessageAttachmentFile | UserMessageAttachmentDirectory | UserMessageAttachmentSelection | UserMessageAttachmentGithubReference | UserMessageAttachmentBlob
+
+
+# Derived user-facing permission prompt details for UI consumers
+PermissionPromptRequest = PermissionPromptRequestCommands | PermissionPromptRequestWrite | PermissionPromptRequestRead | PermissionPromptRequestMcp | PermissionPromptRequestUrl | PermissionPromptRequestMemory | PermissionPromptRequestCustomTool | PermissionPromptRequestPath | PermissionPromptRequestHook | PermissionPromptRequestExtensionManagement | PermissionPromptRequestExtensionPermissionAccess
+
+
+# Details of the permission being requested
+PermissionRequest = PermissionRequestShell | PermissionRequestWrite | PermissionRequestRead | PermissionRequestMcp | PermissionRequestUrl | PermissionRequestMemory | PermissionRequestCustomTool | PermissionRequestHook | PermissionRequestExtensionManagement | PermissionRequestExtensionPermissionAccess
+
+
+# Structured metadata identifying what triggered this notification
+SystemNotification = SystemNotificationAgentCompleted | SystemNotificationAgentIdle | SystemNotificationNewInboxMessage | SystemNotificationShellCompleted | SystemNotificationShellDetachedCompleted | SystemNotificationInstructionDiscovered
+
+
+# The approval to add as a session-scoped rule
+UserToolSessionApproval = UserToolSessionApprovalCommands | UserToolSessionApprovalRead | UserToolSessionApprovalWrite | UserToolSessionApprovalMcp | UserToolSessionApprovalMemory | UserToolSessionApprovalCustomTool | UserToolSessionApprovalExtensionManagement | UserToolSessionApprovalExtensionPermissionAccess
+
+
# The embedded resource contents, either text or base64-encoded binary
ToolExecutionCompleteContentResourceDetails = EmbeddedTextResourceContents | EmbeddedBlobResourceContents
+# The result of the permission request
+PermissionResult = PermissionApproved | PermissionApprovedForSession | PermissionApprovedForLocation | PermissionCancelled | PermissionDeniedByRules | PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser | PermissionDeniedInteractivelyByUser | PermissionDeniedByContentExclusionPolicy | PermissionDeniedByPermissionRequestHook
+
+
class AbortReason(Enum):
"Finite reason code describing why the current turn was aborted"
# The local user requested the abort, for example by pressing Ctrl+C in the CLI.
@@ -4976,21 +6076,6 @@ class ModelCallFailureSource(Enum):
MCP_SAMPLING = "mcp_sampling"
-class PermissionPromptRequestKind(Enum):
- "Derived user-facing permission prompt details for UI consumers discriminator"
- COMMANDS = "commands"
- WRITE = "write"
- READ = "read"
- MCP = "mcp"
- URL = "url"
- MEMORY = "memory"
- CUSTOM_TOOL = "custom-tool"
- PATH = "path"
- HOOK = "hook"
- EXTENSION_MANAGEMENT = "extension-management"
- EXTENSION_PERMISSION_ACCESS = "extension-permission-access"
-
-
class PermissionPromptRequestPathAccessKind(Enum):
"Underlying permission kind that needs path approval"
# Read access to a filesystem path.
@@ -5001,20 +6086,6 @@ class PermissionPromptRequestPathAccessKind(Enum):
WRITE = "write"
-class PermissionRequestKind(Enum):
- "Details of the permission being requested discriminator"
- SHELL = "shell"
- WRITE = "write"
- READ = "read"
- MCP = "mcp"
- URL = "url"
- MEMORY = "memory"
- CUSTOM_TOOL = "custom-tool"
- HOOK = "hook"
- EXTENSION_MANAGEMENT = "extension-management"
- EXTENSION_PERMISSION_ACCESS = "extension-permission-access"
-
-
class PermissionRequestMemoryAction(Enum):
"Whether this is a store or vote memory operation"
# Store a new memory.
@@ -5031,19 +6102,6 @@ class PermissionRequestMemoryDirection(Enum):
DOWNVOTE = "downvote"
-class PermissionResultKind(Enum):
- "The result of the permission request discriminator"
- APPROVED = "approved"
- APPROVED_FOR_SESSION = "approved-for-session"
- APPROVED_FOR_LOCATION = "approved-for-location"
- CANCELLED = "cancelled"
- DENIED_BY_RULES = "denied-by-rules"
- DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user"
- DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user"
- DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy"
- DENIED_BY_PERMISSION_REQUEST_HOOK = "denied-by-permission-request-hook"
-
-
class PlanChangedOperation(Enum):
"The type of operation performed on the plan file"
# The plan file was created.
@@ -5116,16 +6174,6 @@ class SystemNotificationAgentCompletedStatus(Enum):
FAILED = "failed"
-class SystemNotificationType(Enum):
- "Structured metadata identifying what triggered this notification discriminator"
- AGENT_COMPLETED = "agent_completed"
- AGENT_IDLE = "agent_idle"
- NEW_INBOX_MESSAGE = "new_inbox_message"
- SHELL_COMPLETED = "shell_completed"
- SHELL_DETACHED_COMPLETED = "shell_detached_completed"
- INSTRUCTION_DISCOVERED = "instruction_discovered"
-
-
class ToolExecutionCompleteContentResourceLinkIconTheme(Enum):
"Theme variant this icon is intended for"
# Icon intended for light themes.
@@ -5134,16 +6182,6 @@ class ToolExecutionCompleteContentResourceLinkIconTheme(Enum):
DARK = "dark"
-class ToolExecutionCompleteContentType(Enum):
- "A content block within a tool result, which may be text, terminal output, image, audio, or a resource discriminator"
- TEXT = "text"
- TERMINAL = "terminal"
- IMAGE = "image"
- AUDIO = "audio"
- RESOURCE_LINK = "resource_link"
- RESOURCE = "resource"
-
-
class UserMessageAgentMode(Enum):
"The agent mode that was active when this message was sent"
# The agent is responding interactively to the user.
@@ -5166,27 +6204,6 @@ class UserMessageAttachmentGithubReferenceType(Enum):
DISCUSSION = "discussion"
-class UserMessageAttachmentType(Enum):
- "A user message attachment — a file, directory, code selection, blob, or GitHub reference discriminator"
- FILE = "file"
- DIRECTORY = "directory"
- SELECTION = "selection"
- GITHUB_REFERENCE = "github_reference"
- BLOB = "blob"
-
-
-class UserToolSessionApprovalKind(Enum):
- "The approval to add as a session-scoped rule discriminator"
- COMMANDS = "commands"
- READ = "read"
- WRITE = "write"
- MCP = "mcp"
- MEMORY = "memory"
- CUSTOM_TOOL = "custom-tool"
- EXTENSION_MANAGEMENT = "extension-management"
- EXTENSION_PERMISSION_ACCESS = "extension-permission-access"
-
-
class WorkingDirectoryContextHostType(Enum):
"Hosting platform type of the repository (github or ado)"
# Repository is hosted on GitHub.
diff --git a/python/copilot/session.py b/python/copilot/session.py
index caf2e3020..9798835e9 100644
--- a/python/copilot/session.py
+++ b/python/copilot/session.py
@@ -18,6 +18,7 @@
import time
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
+from datetime import UTC, datetime
from types import TracebackType
from typing import TYPE_CHECKING, Any, Literal, NotRequired, Required, TypedDict, cast
@@ -32,8 +33,9 @@
LogRequest,
ModelSwitchToRequest,
PermissionDecision,
- PermissionDecisionKind,
+ PermissionDecisionApproveOnce,
PermissionDecisionRequest,
+ PermissionDecisionUserNotAvailable,
SessionLogLevel,
SessionRpc,
UIElicitationRequest,
@@ -231,19 +233,26 @@ class SystemMessageCustomizeConfig(TypedDict, total=False):
# Permission Types
# ============================================================================
-PermissionRequestResultKind = Literal[
- "approve-once",
- "reject",
- "user-not-available",
- "no-result",
-]
-
@dataclass
-class PermissionRequestResult:
- """Result of a permission request."""
+class PermissionNoResult:
+ """Sentinel returned by a permission handler to leave the request unanswered.
+
+ Only meaningful against protocol-v1 servers. v2 servers reject ``no-result``
+ responses; the SDK raises :class:`ValueError` if a v2 server receives one.
+ Mirrors the ``{kind: "no-result"}`` extension TS adds to its ``PermissionDecision``
+ union (see ``nodejs/src/types.ts:883``).
+ """
+
+ kind: Literal["no-result"] = "no-result"
+
- kind: PermissionRequestResultKind = "user-not-available"
+# The decision returned by a permission handler. Identical shape to the wire
+# ``PermissionDecision`` discriminated union, plus a :class:`PermissionNoResult`
+# sentinel for v1 servers. Construct via the generated variant classes:
+# ``PermissionDecisionApproveOnce(kind=...)``, ``PermissionDecisionReject(kind=...,
+# feedback=...)``, etc.
+PermissionRequestResult = PermissionDecision | PermissionNoResult
_PermissionHandlerFn = Callable[
@@ -257,7 +266,7 @@ class PermissionHandler:
def approve_all(
request: PermissionRequest, invocation: dict[str, str]
) -> PermissionRequestResult:
- return PermissionRequestResult(kind="approve-once")
+ return PermissionDecisionApproveOnce()
# ============================================================================
@@ -619,7 +628,7 @@ class PreToolUseHookInput(TypedDict):
"""Input for pre-tool-use hook"""
sessionId: str
- timestamp: int
+ timestamp: datetime
workingDirectory: str
toolName: str
toolArgs: Any
@@ -645,7 +654,7 @@ class PreMcpToolCallHookInput(TypedDict):
"""Input for pre-MCP-tool-call hook"""
sessionId: str
- timestamp: int
+ timestamp: datetime
workingDirectory: str
serverName: str
toolName: str
@@ -676,7 +685,7 @@ class PostToolUseHookInput(TypedDict):
"""Input for post-tool-use hook"""
sessionId: str
- timestamp: int
+ timestamp: datetime
workingDirectory: str
toolName: str
toolArgs: Any
@@ -701,7 +710,7 @@ class UserPromptSubmittedHookInput(TypedDict):
"""Input for user-prompt-submitted hook"""
sessionId: str
- timestamp: int
+ timestamp: datetime
workingDirectory: str
prompt: str
@@ -724,7 +733,7 @@ class SessionStartHookInput(TypedDict):
"""Input for session-start hook"""
sessionId: str
- timestamp: int
+ timestamp: datetime
workingDirectory: str
source: Literal["startup", "resume", "new"]
initialPrompt: NotRequired[str]
@@ -747,7 +756,7 @@ class SessionEndHookInput(TypedDict):
"""Input for session-end hook"""
sessionId: str
- timestamp: int
+ timestamp: datetime
workingDirectory: str
reason: Literal["complete", "error", "abort", "timeout", "user_exit"]
finalMessage: NotRequired[str]
@@ -772,7 +781,7 @@ class ErrorOccurredHookInput(TypedDict):
"""Input for error-occurred hook"""
sessionId: str
- timestamp: int
+ timestamp: datetime
workingDirectory: str
error: str
errorContext: Literal["model_call", "tool_execution", "system", "user_input"]
@@ -929,193 +938,12 @@ class ProviderConfig(TypedDict, total=False):
# triggers conversation compaction before sending a request when the prompt
# (system message, history, tool definitions, user message) would exceed
# this limit.
- max_input_tokens: int
+ max_prompt_tokens: int
# Overrides the resolved model's default max output tokens. When hit, the
# model stops generating and returns a truncated response.
max_output_tokens: int
-class SessionConfig(TypedDict, total=False):
- """Configuration for creating a session"""
-
- session_id: str # Optional custom session ID
- # Client name to identify the application using the SDK.
- # Included in the User-Agent header for API requests.
- client_name: str
- model: str # Model to use for this session. Use client.list_models() to see available models.
- # Reasoning effort level for models that support it.
- # Only valid for models where capabilities.supports.reasoning_effort is True.
- reasoning_effort: ReasoningEffort
- tools: list[Tool]
- system_message: SystemMessageConfig # System message configuration
- # List of tool names to allow. When specified, only these tools will be available.
- # Applies to the full merged tool catalog (built-in, MCP, and custom tools
- # registered via tools=). Takes precedence over excluded_tools.
- available_tools: list[str]
- # List of tool names to disable. Applies to all tools including custom tools
- # registered via tools=. Ignored if available_tools is set.
- excluded_tools: list[str]
- # Optional handler for permission requests from the server. When omitted,
- # requests are surfaced as events and left pending for manual resolution.
- on_permission_request: _PermissionHandlerFn | None
- # Handler for user input requests from the agent (enables ask_user tool)
- on_user_input_request: UserInputHandler
- # Hook handlers for intercepting session lifecycle events
- hooks: SessionHooks
- # Working directory for the session. Tool operations will be relative to this directory.
- working_directory: str
- # Custom provider configuration (BYOK - Bring Your Own Key)
- provider: ProviderConfig
- # Enables or disables internal session telemetry for this session. When False,
- # disables session telemetry. When omitted (the default) or True, telemetry is enabled for
- # GitHub-authenticated sessions. When a custom provider (BYOK) is configured,
- # session telemetry is always disabled regardless of this setting.
- # This is independent of the client OpenTelemetry configuration.
- enable_session_telemetry: bool
- # Enable streaming of assistant message and reasoning chunks
- # When True, assistant.message_delta and assistant.reasoning_delta events
- # with delta_content are sent as the response is generated
- streaming: bool
- # Include sub-agent streaming events in the event stream. When True, streaming
- # delta events from sub-agents (e.g., assistant.message_delta,
- # assistant.reasoning_delta, assistant.streaming_delta with agentId set) are
- # forwarded to this connection. When False, only non-streaming sub-agent events
- # and subagent.* lifecycle events are forwarded; streaming deltas from sub-agents
- # are suppressed. Defaults to True.
- include_sub_agent_streaming_events: bool
- # MCP server configurations for the session
- mcp_servers: dict[str, MCPServerConfig]
- # Custom agent configurations for the session
- custom_agents: list[CustomAgentConfig]
- # Configuration for the default agent.
- # Use excluded_tools to hide tools from the default agent
- # while keeping them available to sub-agents.
- default_agent: DefaultAgentConfig
- # Name of the custom agent to activate when the session starts.
- # Must match the name of one of the agents in custom_agents.
- agent: str
- # Override the default configuration directory location.
- # When specified, the session will use this directory for storing config and state.
- config_dir: str
- # Directories to load skills from
- skill_directories: list[str]
- # Additional directories to search for custom instruction files.
- instruction_directories: list[str]
- # List of skill names to disable
- disabled_skills: list[str]
- # Infinite session configuration for persistent workspaces and automatic compaction.
- # When enabled (default), sessions automatically manage context limits and persist state.
- # Set to {"enabled": False} to disable.
- infinite_sessions: InfiniteSessionConfig
- # Optional event handler that is registered on the session before the
- # session.create RPC is issued, ensuring early events (e.g. session.start)
- # are delivered. Equivalent to calling session.on(handler) immediately
- # after creation, but executes earlier in the lifecycle so no events are missed.
- on_event: Callable[[SessionEvent], None]
- # Slash commands to register with the session.
- # When the CLI has a TUI, each command appears as /name for the user to invoke.
- commands: list[CommandDefinition]
- # Handler for elicitation requests from the server.
- # When provided, the server calls back to this client for form-based UI dialogs.
- on_elicitation_request: ElicitationHandler
- # Handler for exit-plan-mode requests from the server.
- on_exit_plan_mode: ExitPlanModeHandler
- # Handler for auto-mode-switch requests from the server.
- on_auto_mode_switch: AutoModeSwitchHandler
- # Handler factory for session-scoped sessionFs operations.
- create_session_fs_handler: CreateSessionFsHandler
-
-
-class ResumeSessionConfig(TypedDict, total=False):
- """Configuration for resuming a session"""
-
- # Client name to identify the application using the SDK.
- # Included in the User-Agent header for API requests.
- client_name: str
- # Model to use for this session. Can change the model when resuming.
- model: str
- tools: list[Tool]
- system_message: SystemMessageConfig # System message configuration
- # List of tool names to allow. When specified, only these tools will be available.
- # Applies to the full merged tool catalog (built-in, MCP, and custom tools
- # registered via tools=). Takes precedence over excluded_tools.
- available_tools: list[str]
- # List of tool names to disable. Applies to all tools including custom tools
- # registered via tools=. Ignored if available_tools is set.
- excluded_tools: list[str]
- provider: ProviderConfig
- # Enables or disables internal session telemetry for this session. When False,
- # disables session telemetry. When omitted (the default) or True, telemetry is enabled for
- # GitHub-authenticated sessions. When a custom provider (BYOK) is configured,
- # session telemetry is always disabled regardless of this setting.
- # This is independent of the client OpenTelemetry configuration.
- enable_session_telemetry: bool
- # Reasoning effort level for models that support it.
- reasoning_effort: ReasoningEffort
- # Optional handler for permission requests from the server. When omitted,
- # requests are surfaced as events and left pending for manual resolution.
- on_permission_request: _PermissionHandlerFn | None
- # Handler for user input requestsfrom the agent (enables ask_user tool)
- on_user_input_request: UserInputHandler
- # Hook handlers for intercepting session lifecycle events
- hooks: SessionHooks
- # Working directory for the session. Tool operations will be relative to this directory.
- working_directory: str
- # Override the default configuration directory location.
- config_dir: str
- # Enable streaming of assistant message chunks
- streaming: bool
- # Include sub-agent streaming events in the event stream. When True, streaming
- # delta events from sub-agents (e.g., assistant.message_delta,
- # assistant.reasoning_delta, assistant.streaming_delta with agentId set) are
- # forwarded to this connection. When False, only non-streaming sub-agent events
- # and subagent.* lifecycle events are forwarded; streaming deltas from sub-agents
- # are suppressed. Defaults to True.
- include_sub_agent_streaming_events: bool
- # MCP server configurations for the session
- mcp_servers: dict[str, MCPServerConfig]
- # Custom agent configurations for the session
- custom_agents: list[CustomAgentConfig]
- # Configuration for the default agent.
- default_agent: DefaultAgentConfig
- # Name of the custom agent to activate when the session starts.
- # Must match the name of one of the agents in custom_agents.
- agent: str
- # Directories to load skills from
- skill_directories: list[str]
- # Additional directories to search for custom instruction files.
- instruction_directories: list[str]
- # List of skill names to disable
- disabled_skills: list[str]
- # Infinite session configuration for persistent workspaces and automatic compaction.
- infinite_sessions: InfiniteSessionConfig
- # When True, skips emitting the session.resume event.
- # Useful for reconnecting to a session without triggering resume-related side effects.
- disable_resume: bool
- # When True, instructs the runtime to continue any tool calls or permission prompts
- # that were still pending when the session was last suspended. When False (the
- # default), the runtime treats pending work as interrupted on resume.
- #
- # For permission requests, the runtime re-emits ``permission.requested`` so the
- # registered ``on_permission_request`` handler can re-prompt; for external tool
- # calls, the consumer is expected to supply the result via the corresponding
- # low-level RPC method.
- continue_pending_work: bool
- # Optional event handler registered before the session.resume RPC is issued,
- # ensuring early events are delivered. See SessionConfig.on_event.
- on_event: Callable[[SessionEvent], None]
- # Slash commands to register with the session.
- commands: list[CommandDefinition]
- # Handler for elicitation requests from the server.
- on_elicitation_request: ElicitationHandler
- # Handler for exit-plan-mode requests from the server.
- on_exit_plan_mode: ExitPlanModeHandler
- # Handler for auto-mode-switch requests from the server.
- on_auto_mode_switch: AutoModeSwitchHandler
- # Handler factory for session-scoped sessionFs operations.
- create_session_fs_handler: CreateSessionFsHandler
-
-
SessionEventHandler = Callable[[SessionEvent], None]
@@ -1703,18 +1531,14 @@ async def _execute_permission_and_respond(
)
result = cast(PermissionRequestResult, result)
- if result.kind == "no-result":
+ if isinstance(result, PermissionNoResult):
return
- perm_result = PermissionDecision(
- kind=PermissionDecisionKind(result.kind),
- )
-
rpc_start = time.perf_counter()
await self.rpc.permissions.handle_pending_permission_request(
PermissionDecisionRequest(
request_id=request_id,
- result=perm_result,
+ result=result,
)
)
log_timing(
@@ -1730,9 +1554,7 @@ async def _execute_permission_and_respond(
await self.rpc.permissions.handle_pending_permission_request(
PermissionDecisionRequest(
request_id=request_id,
- result=PermissionDecision(
- kind=PermissionDecisionKind.USER_NOT_AVAILABLE,
- ),
+ result=PermissionDecisionUserNotAvailable(),
)
)
except (JsonRpcError, ProcessExitedError, OSError):
@@ -1995,8 +1817,8 @@ async def _handle_permission_request(
handler = self._permission_handler
if not handler:
- # No handler registered, deny permission
- return PermissionRequestResult()
+ # No handler registered, deny permission.
+ return PermissionDecisionUserNotAvailable()
try:
handler_start = time.perf_counter()
@@ -2012,13 +1834,13 @@ async def _handle_permission_request(
)
return cast(PermissionRequestResult, result)
except Exception: # pylint: disable=broad-except
- # Handler failed, deny permission
+ # Handler failed, deny permission.
logger.debug(
"Error handling permission request",
extra={"session_id": self.session_id},
exc_info=True,
)
- return PermissionRequestResult()
+ return PermissionDecisionUserNotAvailable()
def _register_user_input_handler(self, handler: UserInputHandler | None) -> None:
"""
@@ -2226,9 +2048,21 @@ async def _handle_hooks_invoke(self, hook_type: str, input_data: Any) -> Any:
try:
handler_start = time.perf_counter()
- # Remap wire key "cwd" to public API key "workingDirectory"
- if "cwd" in input_data:
- input_data = {**input_data, "workingDirectory": input_data.pop("cwd")}
+ # Normalize input from the wire format:
+ # - Remap wire key "cwd" to public API key "workingDirectory".
+ # - Convert "timestamp" from epoch milliseconds to ``datetime`` so
+ # hook handlers see a timezone-aware ``datetime`` rather than a
+ # raw integer (matches TS PR #1357 Phase E).
+ transformed: dict[str, Any] = dict(input_data)
+ if "cwd" in transformed:
+ transformed["workingDirectory"] = transformed.pop("cwd")
+ timestamp = transformed.get("timestamp")
+ if isinstance(timestamp, (int, float)):
+ transformed["timestamp"] = datetime.fromtimestamp(timestamp / 1000, tz=UTC)
+ # Each per-hook-type TypedDict is structurally compatible with the
+ # normalized dict; cast to ``Any`` so ty doesn't try to narrow the
+ # specific TypedDict variant from the runtime ``dict``.
+ input_data = cast(Any, transformed)
result = handler(input_data, {"session_id": self.session_id})
if inspect.isawaitable(result):
result = await result
@@ -2250,7 +2084,7 @@ async def _handle_hooks_invoke(self, hook_type: str, input_data: Any) -> Any:
)
return None
- async def get_messages(self) -> list[SessionEvent]:
+ async def get_events(self) -> list[SessionEvent]:
"""
Retrieve all events and messages from this session's history.
@@ -2265,7 +2099,7 @@ async def get_messages(self) -> list[SessionEvent]:
Example:
>>> from copilot.generated.session_events import AssistantMessageData
- >>> events = await session.get_messages()
+ >>> events = await session.get_events()
>>> for event in events:
... match event.data:
... case AssistantMessageData() as data:
@@ -2325,26 +2159,6 @@ async def disconnect(self) -> None:
with self._auto_mode_switch_handler_lock:
self._auto_mode_switch_handler = None
- async def destroy(self) -> None:
- """
- .. deprecated::
- Use :meth:`disconnect` instead. This method will be removed in a future release.
-
- Disconnect this session and release all in-memory resources.
- Session data on disk is preserved for later resumption.
-
- Raises:
- Exception: If the connection fails.
- """
- import warnings
-
- warnings.warn(
- "destroy() is deprecated, use disconnect() instead",
- DeprecationWarning,
- stacklevel=2,
- )
- await self.disconnect()
-
async def __aenter__(self) -> CopilotSession:
"""Enable use as an async context manager."""
return self
diff --git a/python/copilot/tools.py b/python/copilot/tools.py
index 3f8eb9c1b..c6a29dc61 100644
--- a/python/copilot/tools.py
+++ b/python/copilot/tools.py
@@ -24,7 +24,7 @@ class ToolBinaryResult:
data: str = ""
mime_type: str = ""
- type: str = ""
+ type: Literal["image", "resource"] = "image"
description: str = ""
diff --git a/python/e2e/test_agent_and_compact_rpc_e2e.py b/python/e2e/test_agent_and_compact_rpc_e2e.py
index f4773a798..14ea01ff2 100644
--- a/python/e2e/test_agent_and_compact_rpc_e2e.py
+++ b/python/e2e/test_agent_and_compact_rpc_e2e.py
@@ -4,8 +4,7 @@
import pytest
-from copilot import CopilotClient
-from copilot.client import SubprocessConfig
+from copilot import CopilotClient, RuntimeConnection
from copilot.generated.rpc import AgentSelectRequest
from copilot.session import PermissionHandler
@@ -18,7 +17,7 @@ class TestAgentSelectionRpc:
@pytest.mark.asyncio
async def test_should_list_available_custom_agents(self):
"""Test listing available custom agents via RPC."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
try:
await client.start()
@@ -56,7 +55,7 @@ async def test_should_list_available_custom_agents(self):
@pytest.mark.asyncio
async def test_should_return_null_when_no_agent_is_selected(self):
"""Test getCurrent returns null when no agent is selected."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
try:
await client.start()
@@ -83,7 +82,7 @@ async def test_should_return_null_when_no_agent_is_selected(self):
@pytest.mark.asyncio
async def test_should_select_and_get_current_agent(self):
"""Test selecting an agent and verifying getCurrent returns it."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
try:
await client.start()
@@ -118,7 +117,7 @@ async def test_should_select_and_get_current_agent(self):
@pytest.mark.asyncio
async def test_should_deselect_current_agent(self):
"""Test deselecting the current agent."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
try:
await client.start()
@@ -150,7 +149,7 @@ async def test_should_deselect_current_agent(self):
@pytest.mark.asyncio
async def test_should_return_empty_list_when_no_custom_agents_configured(self):
"""Test listing agents returns no custom agents when none configured."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
try:
await client.start()
@@ -175,7 +174,7 @@ async def test_should_return_empty_list_when_no_custom_agents_configured(self):
@pytest.mark.asyncio
async def test_should_call_agent_reload(self):
"""Test reloading agents via RPC."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
reload_agent = {
"name": f"reload-test-agent-{uuid.uuid4().hex}",
"display_name": "Reload Agent",
diff --git a/python/e2e/test_client_api_e2e.py b/python/e2e/test_client_api_e2e.py
index 1699bb8cf..217352817 100644
--- a/python/e2e/test_client_api_e2e.py
+++ b/python/e2e/test_client_api_e2e.py
@@ -44,6 +44,13 @@ async def test_should_get_null_last_session_id_before_any_sessions_exist(
self, ctx: E2ETestContext
):
await ctx.client.start()
+
+ # Other tests in this class create sessions, and pytest doesn't
+ # guarantee test execution order. Clear any leftover sessions so this
+ # test sees a genuinely empty state regardless of order.
+ for existing in await ctx.client.list_sessions():
+ await ctx.client.delete_session(existing.session_id)
+
result = await ctx.client.get_last_session_id()
assert result is None
diff --git a/python/e2e/test_client_e2e.py b/python/e2e/test_client_e2e.py
index fc7315a58..1e8ea82e5 100644
--- a/python/e2e/test_client_e2e.py
+++ b/python/e2e/test_client_e2e.py
@@ -2,14 +2,13 @@
import pytest
-from copilot import CopilotClient
+from copilot import CopilotClient, RuntimeConnection
from copilot.client import (
ModelCapabilities,
ModelInfo,
ModelLimits,
ModelSupports,
StopError,
- SubprocessConfig,
)
from copilot.session import PermissionHandler
@@ -19,35 +18,31 @@
class TestClient:
@pytest.mark.asyncio
async def test_should_start_and_connect_to_server_using_stdio(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
try:
await client.start()
- assert client.get_state() == "connected"
pong = await client.ping("test message")
assert pong.message == "pong: test message"
assert pong.timestamp is not None
await client.stop()
- assert client.get_state() == "disconnected"
finally:
await client.force_stop()
@pytest.mark.asyncio
async def test_should_start_and_connect_to_server_using_tcp(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=False))
+ client = CopilotClient(connection=RuntimeConnection.for_tcp(path=CLI_PATH))
try:
await client.start()
- assert client.get_state() == "connected"
pong = await client.ping("test message")
assert pong.message == "pong: test message"
assert pong.timestamp is not None
await client.stop()
- assert client.get_state() == "disconnected"
finally:
await client.force_stop()
@@ -55,7 +50,7 @@ async def test_should_start_and_connect_to_server_using_tcp(self):
async def test_should_raise_exception_group_on_failed_cleanup(self):
import asyncio
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
try:
await client.create_session(on_permission_request=PermissionHandler.approve_all)
@@ -72,22 +67,19 @@ async def test_should_raise_exception_group_on_failed_cleanup(self):
assert len(exc.exceptions) > 0
assert isinstance(exc.exceptions[0], StopError)
assert "Failed to disconnect session" in exc.exceptions[0].message
- else:
- assert client.get_state() == "disconnected"
finally:
await client.force_stop()
@pytest.mark.asyncio
async def test_should_force_stop_without_cleanup(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.create_session(on_permission_request=PermissionHandler.approve_all)
await client.force_stop()
- assert client.get_state() == "disconnected"
@pytest.mark.asyncio
async def test_should_get_status_with_version_and_protocol_info(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
try:
await client.start()
@@ -95,9 +87,9 @@ async def test_should_get_status_with_version_and_protocol_info(self):
status = await client.get_status()
assert hasattr(status, "version")
assert isinstance(status.version, str)
- assert hasattr(status, "protocolVersion")
- assert isinstance(status.protocolVersion, int)
- assert status.protocolVersion >= 1
+ assert hasattr(status, "protocol_version")
+ assert isinstance(status.protocol_version, int)
+ assert status.protocol_version >= 1
await client.stop()
finally:
@@ -105,7 +97,7 @@ async def test_should_get_status_with_version_and_protocol_info(self):
@pytest.mark.asyncio
async def test_should_get_auth_status(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
try:
await client.start()
@@ -123,7 +115,7 @@ async def test_should_get_auth_status(self):
@pytest.mark.asyncio
async def test_should_list_models_when_authenticated(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
try:
await client.start()
@@ -151,7 +143,7 @@ async def test_should_list_models_when_authenticated(self):
@pytest.mark.asyncio
async def test_should_cache_models_list(self):
"""Test that list_models caches results to avoid rate limiting"""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
try:
await client.start()
@@ -196,10 +188,8 @@ async def test_should_cache_models_list(self):
async def test_should_report_error_with_stderr_when_cli_fails_to_start(self):
"""Test that CLI startup errors include stderr output in the error message."""
client = CopilotClient(
- SubprocessConfig(
- cli_path=CLI_PATH,
- cli_args=["--nonexistent-flag-for-testing"],
- use_stdio=True,
+ connection=RuntimeConnection.for_stdio(
+ path=CLI_PATH, args=["--nonexistent-flag-for-testing"]
)
)
@@ -231,7 +221,7 @@ async def test_should_report_error_with_stderr_when_cli_fails_to_start(self):
@pytest.mark.asyncio
async def test_should_not_throw_when_disposing_session_after_stopping_client(self):
"""Disconnecting a session after the client is stopped must not raise."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
try:
await client.start()
@@ -250,7 +240,7 @@ async def test_should_not_throw_when_disposing_session_after_stopping_client(sel
@pytest.mark.asyncio
async def test_should_create_session_without_permission_handler(self):
"""`create_session` allows omitting an `on_permission_request` handler."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
try:
await client.start()
@@ -265,7 +255,7 @@ async def test_should_create_session_without_permission_handler(self):
@pytest.mark.asyncio
async def test_should_resume_session_without_permission_handler(self):
"""`resume_session` allows omitting an `on_permission_request` handler."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
try:
await client.start()
@@ -300,7 +290,7 @@ def on_list_models():
return custom_models
client = CopilotClient(
- SubprocessConfig(cli_path=CLI_PATH, use_stdio=True),
+ connection=RuntimeConnection.for_stdio(path=CLI_PATH),
on_list_models=on_list_models,
)
@@ -338,7 +328,7 @@ def on_list_models():
return custom_models
client = CopilotClient(
- SubprocessConfig(cli_path=CLI_PATH, use_stdio=True),
+ connection=RuntimeConnection.for_stdio(path=CLI_PATH),
on_list_models=on_list_models,
)
diff --git a/python/e2e/test_client_lifecycle_e2e.py b/python/e2e/test_client_lifecycle_e2e.py
index 90b96d822..d5a2fb681 100644
--- a/python/e2e/test_client_lifecycle_e2e.py
+++ b/python/e2e/test_client_lifecycle_e2e.py
@@ -1,5 +1,5 @@
"""
-Client lifecycle tests covering ``client.on(...)`` lifecycle event subscriptions
+Client lifecycle tests covering ``client.on_lifecycle(...)`` lifecycle event subscriptions
and connection-state transitions across ``start``/``stop``.
Mirrors ``dotnet/test/ClientLifecycleTests.cs`` plus the existing ``client_lifecycle``
@@ -14,8 +14,7 @@
import pytest
-from copilot import CopilotClient
-from copilot.client import SubprocessConfig
+from copilot import CopilotClient, RuntimeConnection
from copilot.session import PermissionHandler
from .testharness import E2ETestContext
@@ -60,12 +59,10 @@ def _make_isolated_client(ctx: E2ETestContext) -> CopilotClient:
"fake-token-for-e2e-tests" if os.environ.get("GITHUB_ACTIONS") == "true" else None
)
return CopilotClient(
- SubprocessConfig(
- cli_path=ctx.cli_path,
- working_directory=ctx.work_dir,
- env=ctx.get_env(),
- github_token=github_token,
- )
+ connection=RuntimeConnection.for_stdio(path=ctx.cli_path),
+ working_directory=ctx.work_dir,
+ env=ctx.get_env(),
+ github_token=github_token,
)
@@ -84,7 +81,7 @@ async def test_should_return_last_session_id_after_sending_a_message(self, ctx:
async def test_should_emit_session_lifecycle_events(self, ctx: E2ETestContext):
events: list = []
- unsubscribe = ctx.client.on(events.append)
+ unsubscribe = ctx.client.on_lifecycle(events.append)
try:
session = await ctx.client.create_session(
on_permission_request=PermissionHandler.approve_all,
@@ -94,7 +91,7 @@ async def test_should_emit_session_lifecycle_events(self, ctx: E2ETestContext):
await _wait_for_condition(
lambda: any(
- getattr(e, "sessionId", None) == session.session_id for e in events
+ getattr(e, "session_id", None) == session.session_id for e in events
),
timeout=10.0,
)
@@ -111,7 +108,7 @@ def handler(event):
if event.type == "session.created" and not created.done():
created.set_result(event)
- unsubscribe = ctx.client.on(handler)
+ unsubscribe = ctx.client.on_lifecycle(handler)
try:
session = await ctx.client.create_session(
on_permission_request=PermissionHandler.approve_all,
@@ -119,7 +116,7 @@ def handler(event):
try:
event = await asyncio.wait_for(created, 10.0)
assert event.type == "session.created"
- assert event.sessionId == session.session_id
+ assert event.session_id == session.session_id
finally:
await session.disconnect()
finally:
@@ -133,7 +130,7 @@ def handler(event):
if not created.done():
created.set_result(event)
- unsubscribe = ctx.client.on("session.created", handler)
+ unsubscribe = ctx.client.on_lifecycle("session.created", handler)
try:
session = await ctx.client.create_session(
on_permission_request=PermissionHandler.approve_all,
@@ -141,7 +138,7 @@ def handler(event):
try:
event = await asyncio.wait_for(created, 10.0)
assert event.type == "session.created"
- assert event.sessionId == session.session_id
+ assert event.session_id == session.session_id
finally:
await session.disconnect()
finally:
@@ -157,11 +154,11 @@ def disposed_handler(_event):
nonlocal unsubscribed_count
unsubscribed_count += 1
- unsubscribe_disposed = ctx.client.on(disposed_handler)
+ unsubscribe_disposed = ctx.client.on_lifecycle(disposed_handler)
unsubscribe_disposed() # Immediately dispose first subscription.
active_event: asyncio.Future = loop.create_future()
- unsubscribe_active = ctx.client.on(
+ unsubscribe_active = ctx.client.on_lifecycle(
"session.created",
lambda evt: active_event.set_result(evt) if not active_event.done() else None,
)
@@ -171,7 +168,7 @@ def disposed_handler(_event):
)
try:
event = await asyncio.wait_for(active_event, 10.0)
- assert event.sessionId == session.session_id
+ assert event.session_id == session.session_id
assert unsubscribed_count == 0, "Disposed handler should not have fired"
finally:
await session.disconnect()
@@ -181,12 +178,7 @@ def disposed_handler(_event):
async def test_stop_disconnects_client_and_disposes_rpc_surface(self, ctx: E2ETestContext):
client = _make_isolated_client(ctx)
await client.start()
- try:
- assert client.get_state() == "connected"
- finally:
- await client.stop()
-
- assert client.get_state() == "disconnected"
+ await client.stop()
with pytest.raises(RuntimeError):
_ = client.rpc
@@ -207,17 +199,17 @@ async def test_should_receive_session_updated_lifecycle_event_for_non_ephemeral_
def handler(event):
if (
event.type == "session.updated"
- and event.sessionId == session.session_id
+ and event.session_id == session.session_id
and not updated.done()
):
updated.set_result(event)
- unsubscribe = ctx.client.on(handler)
+ unsubscribe = ctx.client.on_lifecycle(handler)
try:
await session.rpc.mode.set(ModeSetRequest(mode=SessionMode.PLAN))
event = await asyncio.wait_for(updated, timeout=15.0)
assert event.type == "session.updated"
- assert event.sessionId == session.session_id
+ assert event.session_id == session.session_id
finally:
unsubscribe()
await session.disconnect()
@@ -242,18 +234,18 @@ async def test_should_receive_session_deleted_lifecycle_event_when_deleted(
def handler(event):
if (
event.type == "session.deleted"
- and event.sessionId == session_id
+ and event.session_id == session_id
and not deleted.done()
):
deleted.set_result(event)
- unsubscribe = ctx.client.on(handler)
+ unsubscribe = ctx.client.on_lifecycle(handler)
try:
await session.disconnect()
await ctx.client.delete_session(session_id)
event = await asyncio.wait_for(deleted, timeout=15.0)
assert event.type == "session.deleted"
- assert event.sessionId == session_id
+ assert event.session_id == session_id
finally:
unsubscribe()
diff --git a/python/e2e/test_client_options_e2e.py b/python/e2e/test_client_options_e2e.py
index 80a3bf394..614aec5df 100644
--- a/python/e2e/test_client_options_e2e.py
+++ b/python/e2e/test_client_options_e2e.py
@@ -1,14 +1,15 @@
"""
E2E coverage for ``CopilotClient`` configuration options exposed via
-``SubprocessConfig`` and ``CopilotClient(..., auto_start=...)``.
+``CopilotClientOptions`` and ``RuntimeConnection``.
Mirrors ``dotnet/test/ClientOptionsTests.cs``. The two CliUrl-conflict tests
(``Should_Throw_When_GitHubToken_Used_With_CliUrl`` and
``Should_Throw_When_UseLoggedInUser_Used_With_CliUrl``) have no Python
-equivalent because Python's ``ExternalServerConfig`` does not accept
-``github_token`` / ``use_logged_in_user`` fields at all (so the conflict cannot
-be expressed in code), and the configurations are therefore intentionally
-omitted.
+equivalent because Python's ``RuntimeConnection.for_uri(...)`` does not accept
+``github_token`` / ``use_logged_in_user`` fields at all (those live on
+``CopilotClientOptions``, but a Uri-connected runtime ignores them), so the
+conflict cannot be expressed in code and the configurations are therefore
+intentionally omitted.
"""
from __future__ import annotations
@@ -19,8 +20,7 @@
import pytest
-from copilot import CopilotClient
-from copilot.client import SubprocessConfig
+from copilot import CopilotClient, RuntimeConnection
from copilot.generated.rpc import PingRequest
from copilot.session import PermissionHandler
@@ -29,9 +29,31 @@
pytestmark = pytest.mark.asyncio(loop_scope="module")
-def _make_subprocess_config(ctx: E2ETestContext, **overrides) -> SubprocessConfig:
- base = {
- "cli_path": ctx.cli_path,
+def _make_options(
+ ctx: E2ETestContext,
+ *,
+ use_tcp: bool = False,
+ port: int = 0,
+ connection_token: str | None = None,
+ cli_path: str | None = None,
+ cli_args: list[str] | None = None,
+ **overrides,
+) -> dict[str, object]:
+ """Build CopilotClient kwargs pre-populated for the test harness."""
+ if use_tcp:
+ connection: RuntimeConnection = RuntimeConnection.for_tcp(
+ port=port,
+ connection_token=connection_token,
+ path=cli_path if cli_path is not None else ctx.cli_path,
+ args=tuple(cli_args or []),
+ )
+ else:
+ connection = RuntimeConnection.for_stdio(
+ path=cli_path if cli_path is not None else ctx.cli_path,
+ args=tuple(cli_args or []),
+ )
+ base: dict[str, object] = {
+ "connection": connection,
"working_directory": ctx.work_dir,
"env": ctx.get_env(),
"github_token": (
@@ -39,7 +61,7 @@ def _make_subprocess_config(ctx: E2ETestContext, **overrides) -> SubprocessConfi
),
}
base.update(overrides)
- return SubprocessConfig(**base)
+ return base
def _get_available_port() -> int:
@@ -120,7 +142,7 @@ def _get_available_port() -> int:
return;
}
if (message.method === "session.create") {
- const sessionId = message.params?.sessionId ?? "fake-session";
+ const sessionId = message.params?.session_id ?? "fake-session";
writeResponse(message.id, { sessionId, workspacePath: null, capabilities: null });
return;
}
@@ -142,39 +164,12 @@ def _assert_arg_value(args: list[str], name: str, expected_value: str) -> None:
class TestClientOptions:
- async def test_autostart_false_requires_explicit_start(self, ctx: E2ETestContext):
- client = CopilotClient(_make_subprocess_config(ctx), auto_start=False)
- try:
- assert client.get_state() == "disconnected"
-
- with pytest.raises(RuntimeError) as exc_info:
- await client.create_session(
- on_permission_request=PermissionHandler.approve_all,
- )
- # Python raises "Client not connected" — equivalent intent to C#'s "StartAsync".
- assert (
- "not connected" in str(exc_info.value).lower()
- or "start" in str(exc_info.value).lower()
- )
-
- await client.start()
- assert client.get_state() == "connected"
-
- session = await client.create_session(
- on_permission_request=PermissionHandler.approve_all,
- )
- assert session.session_id
- await session.disconnect()
- finally:
- await client.stop()
-
async def test_should_listen_on_configured_tcp_port(self, ctx: E2ETestContext):
port = _get_available_port()
- client = CopilotClient(_make_subprocess_config(ctx, use_stdio=False, port=port))
+ client = CopilotClient(**_make_options(ctx, use_tcp=True, port=port))
try:
await client.start()
- assert client.get_state() == "connected"
- assert client.actual_port == port
+ assert client.runtime_port == port
response = await client.rpc.ping(PingRequest(message="fixed-port"))
assert "pong" in response.message
@@ -187,7 +182,7 @@ async def test_should_use_client_cwd_for_default_workingdirectory(self, ctx: E2E
with open(os.path.join(client_cwd, "marker.txt"), "w") as f:
f.write("I am in the client cwd")
- client = CopilotClient(_make_subprocess_config(ctx, working_directory=client_cwd))
+ client = CopilotClient(**_make_options(ctx, working_directory=client_cwd))
try:
session = await client.create_session(
on_permission_request=PermissionHandler.approve_all,
@@ -212,10 +207,10 @@ async def test_should_propagate_process_options_to_spawned_cli(self, ctx: E2ETes
f.write(FAKE_STDIO_CLI_SCRIPT)
client = CopilotClient(
- _make_subprocess_config(
+ **_make_options(
ctx,
cli_path=cli_path,
- copilot_home=copilot_home_from_option,
+ base_directory=copilot_home_from_option,
cli_args=["--capture-file", capture_path],
env={**ctx.get_env(), "COPILOT_HOME": copilot_home_from_env},
github_token="process-option-token",
@@ -230,7 +225,6 @@ async def test_should_propagate_process_options_to_spawned_cli(self, ctx: E2ETes
},
use_logged_in_user=False,
),
- auto_start=False,
)
try:
await client.start()
@@ -278,51 +272,3 @@ async def test_should_propagate_process_options_to_spawned_cli(self, ctx: E2ETes
await client.stop()
except Exception:
await client.force_stop()
-
-
-# ---------------------------------------------------------------------------
-# Unit-style tests mirroring the property-only tests in
-# dotnet/test/ClientOptionsTests.cs. These exercise the SubprocessConfig
-# dataclass shape only — no client / proxy required.
-# ---------------------------------------------------------------------------
-
-
-class TestSubprocessConfigOptions:
- """Mirrors the unit-style ClientOptions tests in the C# baseline."""
-
- async def test_should_accept_github_token_option(self):
- # Mirrors: Should_Accept_GitHubToken_Option
- config = SubprocessConfig(github_token="gho_test_token")
- assert config.github_token == "gho_test_token"
-
- async def test_should_default_use_logged_in_user_to_none(self):
- # Mirrors: Should_Default_UseLoggedInUser_To_Null
- config = SubprocessConfig()
- assert config.use_logged_in_user is None
-
- async def test_should_allow_explicit_use_logged_in_user_false(self):
- # Mirrors: Should_Allow_Explicit_UseLoggedInUser_False
- config = SubprocessConfig(use_logged_in_user=False)
- assert config.use_logged_in_user is False
-
- async def test_should_allow_explicit_use_logged_in_user_true_with_github_token(self):
- # Mirrors: Should_Allow_Explicit_UseLoggedInUser_True_With_GitHubToken
- config = SubprocessConfig(github_token="gho_test_token", use_logged_in_user=True)
- assert config.use_logged_in_user is True
- assert config.github_token == "gho_test_token"
-
- # NOTE: Should_Throw_When_GitHubToken_Used_With_CliUrl and
- # Should_Throw_When_UseLoggedInUser_Used_With_CliUrl from the C# baseline
- # do not apply to Python: ExternalServerConfig has no github_token /
- # use_logged_in_user fields at all (they live only on SubprocessConfig),
- # so the conflicting configuration is impossible to express.
-
- async def test_should_default_session_idle_timeout_seconds_to_none(self):
- # Mirrors: Should_Default_SessionIdleTimeoutSeconds_To_Null
- config = SubprocessConfig()
- assert config.session_idle_timeout_seconds is None
-
- async def test_should_accept_session_idle_timeout_seconds_option(self):
- # Mirrors: Should_Accept_SessionIdleTimeoutSeconds_Option
- config = SubprocessConfig(session_idle_timeout_seconds=600)
- assert config.session_idle_timeout_seconds == 600
diff --git a/python/e2e/test_commands_e2e.py b/python/e2e/test_commands_e2e.py
index 5bf1a274e..e0a0d63f1 100644
--- a/python/e2e/test_commands_e2e.py
+++ b/python/e2e/test_commands_e2e.py
@@ -15,8 +15,7 @@
import pytest
import pytest_asyncio
-from copilot import CopilotClient
-from copilot.client import ExternalServerConfig, SubprocessConfig
+from copilot import CopilotClient, RuntimeConnection
from copilot.session import CommandDefinition, PermissionHandler
from .testharness.context import SNAPSHOTS_DIR, get_cli_path_for_tests
@@ -26,7 +25,7 @@
# ---------------------------------------------------------------------------
-# Multi-client context (TCP mode) — same pattern as test_multi_client.py
+# Multi-client context (TCP mode) — same pattern as test_multi_client.py
# ---------------------------------------------------------------------------
@@ -56,14 +55,12 @@ async def setup(self):
# Client 1 uses TCP mode so a second client can connect
self._client1 = CopilotClient(
- SubprocessConfig(
- cli_path=self.cli_path,
- working_directory=self.work_dir,
- env=self._get_env(),
- use_stdio=False,
- github_token=github_token,
- tcp_connection_token="py-tcp-shared-test-token",
- )
+ connection=RuntimeConnection.for_tcp(
+ path=self.cli_path, connection_token="py-tcp-shared-test-token"
+ ),
+ working_directory=self.work_dir,
+ env=self._get_env(),
+ github_token=github_token,
)
# Trigger connection to get the port
@@ -72,12 +69,12 @@ async def setup(self):
)
await init_session.disconnect()
- actual_port = self._client1.actual_port
+ actual_port = self._client1.runtime_port
assert actual_port is not None
self._client2 = CopilotClient(
- ExternalServerConfig(
- url=f"localhost:{actual_port}", tcp_connection_token="py-tcp-shared-test-token"
+ connection=RuntimeConnection.for_uri(
+ f"localhost:{actual_port}", connection_token="py-tcp-shared-test-token"
)
)
diff --git a/python/e2e/test_connection_token.py b/python/e2e/test_connection_token.py
index 195baaecc..1c7addbd9 100644
--- a/python/e2e/test_connection_token.py
+++ b/python/e2e/test_connection_token.py
@@ -11,8 +11,7 @@
import pytest
import pytest_asyncio
-from copilot import CopilotClient
-from copilot.client import ExternalServerConfig, SubprocessConfig
+from copilot import CopilotClient, RuntimeConnection
from copilot.session import PermissionHandler
from .testharness.proxy import CapiProxy
@@ -47,14 +46,10 @@ async def setup(self):
)
self._client = CopilotClient(
- SubprocessConfig(
- cli_path=self.cli_path,
- working_directory=self.work_dir,
- env=self.get_env(),
- use_stdio=False,
- tcp_connection_token=self.token,
- github_token=github_token,
- )
+ connection=RuntimeConnection.for_tcp(path=self.cli_path, connection_token=self.token),
+ working_directory=self.work_dir,
+ env=self.get_env(),
+ github_token=github_token,
)
# Trigger the spawn + connect handshake so the server is listening.
@@ -133,11 +128,11 @@ async def test_auto_generated_token_round_trips(self, auto_token_ctx: Connection
async def test_wrong_token_is_rejected(self, explicit_token_ctx: ConnectionTokenContext):
"""A sibling client connecting with the wrong token is rejected."""
- port = explicit_token_ctx.client.actual_port
+ port = explicit_token_ctx.client.runtime_port
assert port is not None
wrong = CopilotClient(
- ExternalServerConfig(url=f"localhost:{port}", tcp_connection_token="wrong")
+ connection=RuntimeConnection.for_uri(f"localhost:{port}", connection_token="wrong")
)
try:
with pytest.raises(Exception, match="AUTHENTICATION_FAILED"):
@@ -152,10 +147,10 @@ async def test_wrong_token_is_rejected(self, explicit_token_ctx: ConnectionToken
async def test_missing_token_is_rejected(self, explicit_token_ctx: ConnectionTokenContext):
"""A sibling client with no token is rejected when the server requires one."""
- port = explicit_token_ctx.client.actual_port
+ port = explicit_token_ctx.client.runtime_port
assert port is not None
- no_token = CopilotClient(ExternalServerConfig(url=f"localhost:{port}"))
+ no_token = CopilotClient(connection=RuntimeConnection.for_uri(f"localhost:{port}"))
try:
with pytest.raises(Exception, match="AUTHENTICATION_FAILED"):
await no_token.start()
diff --git a/python/e2e/test_error_resilience_e2e.py b/python/e2e/test_error_resilience_e2e.py
index 4afb78a6e..ab031842c 100644
--- a/python/e2e/test_error_resilience_e2e.py
+++ b/python/e2e/test_error_resilience_e2e.py
@@ -30,7 +30,7 @@ async def test_should_throw_when_getting_messages_from_disconnected_session(
await session.disconnect()
with pytest.raises(Exception):
- await session.get_messages()
+ await session.get_events()
async def test_should_handle_double_abort_without_error(self, ctx: E2ETestContext):
session = await ctx.client.create_session(
diff --git a/python/e2e/test_event_fidelity_e2e.py b/python/e2e/test_event_fidelity_e2e.py
index 17193a308..b85609640 100644
--- a/python/e2e/test_event_fidelity_e2e.py
+++ b/python/e2e/test_event_fidelity_e2e.py
@@ -207,7 +207,7 @@ async def test_should_preserve_message_order_in_getmessages_after_tool_use(
try:
await session.send_and_wait("Read the file 'order.txt' and tell me what the number is.")
- messages = await session.get_messages()
+ messages = await session.get_events()
types = [m.type.value for m in messages]
# Verify complete event ordering contract:
diff --git a/python/e2e/test_mode_handlers_e2e.py b/python/e2e/test_mode_handlers_e2e.py
index c0e19da13..d9917e9f3 100644
--- a/python/e2e/test_mode_handlers_e2e.py
+++ b/python/e2e/test_mode_handlers_e2e.py
@@ -35,7 +35,7 @@
async def mode_ctx(ctx: E2ETestContext):
"""Configure per-token user responses for mode-handler tests."""
proxy_url = ctx.proxy_url
- ctx.client._config.env["COPILOT_DEBUG_GITHUB_API_URL"] = proxy_url
+ ctx.client._options.env["COPILOT_DEBUG_GITHUB_API_URL"] = proxy_url
await ctx.set_copilot_user_by_token(
MODE_HANDLER_TOKEN,
@@ -75,7 +75,7 @@ async def test_should_invoke_exit_plan_mode_handler_when_model_uses_tool(
):
exit_plan_mode_requests = []
- async def on_exit_plan_mode(request, invocation):
+ async def on_exit_plan_mode_request(request, invocation):
exit_plan_mode_requests.append(request)
assert invocation["session_id"] == session.session_id
return {
@@ -87,7 +87,7 @@ async def on_exit_plan_mode(request, invocation):
session = await mode_ctx.client.create_session(
github_token=MODE_HANDLER_TOKEN,
on_permission_request=PermissionHandler.approve_all,
- on_exit_plan_mode=on_exit_plan_mode,
+ on_exit_plan_mode_request=on_exit_plan_mode_request,
)
try:
@@ -139,7 +139,7 @@ async def test_should_invoke_auto_mode_switch_handler_when_rate_limited(
):
auto_mode_switch_requests = []
- async def on_auto_mode_switch(request, invocation):
+ async def on_auto_mode_switch_request(request, invocation):
auto_mode_switch_requests.append(request)
assert invocation["session_id"] == session.session_id
return "yes"
@@ -147,7 +147,7 @@ async def on_auto_mode_switch(request, invocation):
session = await mode_ctx.client.create_session(
github_token=MODE_HANDLER_TOKEN,
on_permission_request=PermissionHandler.approve_all,
- on_auto_mode_switch=on_auto_mode_switch,
+ on_auto_mode_switch_request=on_auto_mode_switch_request,
)
try:
diff --git a/python/e2e/test_multi_client_e2e.py b/python/e2e/test_multi_client_e2e.py
index 06f671e94..deadbfc86 100644
--- a/python/e2e/test_multi_client_e2e.py
+++ b/python/e2e/test_multi_client_e2e.py
@@ -14,9 +14,9 @@
import pytest_asyncio
from pydantic import BaseModel, Field
-from copilot import CopilotClient, define_tool
-from copilot.client import ExternalServerConfig, SubprocessConfig
-from copilot.session import PermissionHandler, PermissionRequestResult
+from copilot import CopilotClient, RuntimeConnection, define_tool
+from copilot.generated.rpc import PermissionDecisionApproveOnce, PermissionDecisionReject
+from copilot.session import PermissionHandler, PermissionNoResult
from copilot.tools import ToolInvocation
from .testharness import get_final_assistant_message
@@ -53,14 +53,12 @@ async def setup(self):
# Client 1 uses TCP mode so a second client can connect to the same server
self._client1 = CopilotClient(
- SubprocessConfig(
- cli_path=self.cli_path,
- working_directory=self.work_dir,
- env=self.get_env(),
- use_stdio=False,
- github_token=github_token,
- tcp_connection_token="py-tcp-shared-test-token",
- )
+ connection=RuntimeConnection.for_tcp(
+ path=self.cli_path, connection_token="py-tcp-shared-test-token"
+ ),
+ working_directory=self.work_dir,
+ env=self.get_env(),
+ github_token=github_token,
)
# Trigger connection by creating and disconnecting an init session
@@ -70,12 +68,12 @@ async def setup(self):
await init_session.disconnect()
# Read the actual port from client 1 and create client 2
- actual_port = self._client1.actual_port
+ actual_port = self._client1.runtime_port
assert actual_port is not None, "Client 1 should have an actual port after connecting"
self._client2 = CopilotClient(
- ExternalServerConfig(
- url=f"localhost:{actual_port}", tcp_connection_token="py-tcp-shared-test-token"
+ connection=RuntimeConnection.for_uri(
+ f"localhost:{actual_port}", connection_token="py-tcp-shared-test-token"
)
)
@@ -204,7 +202,7 @@ def magic_number(params: SeedParams, invocation: ToolInvocation) -> str:
on_permission_request=PermissionHandler.approve_all, tools=[magic_number]
)
- # Client 2 resumes with NO tools — should not overwrite client 1's tools
+ # Client 2 resumes with NO tools — should not overwrite client 1's tools
session2 = await mctx.client2.resume_session(
session1.session_id, on_permission_request=PermissionHandler.approve_all
)
@@ -242,16 +240,14 @@ async def test_one_client_approves_permission_and_both_see_the_result(
# Client 1 creates a session and manually approves permission requests
session1 = await mctx.client1.create_session(
on_permission_request=lambda request, invocation: (
- permission_requests.append(request) or PermissionRequestResult(kind="approve-once")
+ permission_requests.append(request) or PermissionDecisionApproveOnce()
),
)
# Client 2 observes the permission request but leaves the decision to client 1.
session2 = await mctx.client2.resume_session(
session1.session_id,
- on_permission_request=lambda request, invocation: PermissionRequestResult(
- kind="no-result"
- ),
+ on_permission_request=lambda request, invocation: PermissionNoResult(),
)
client1_events = []
@@ -279,7 +275,7 @@ async def test_one_client_approves_permission_and_both_see_the_result(
assert len(c1_perm_completed) > 0
assert len(c2_perm_completed) > 0
for event in c1_perm_completed + c2_perm_completed:
- assert event.data.result.kind.value == "approved"
+ assert event.data.result.kind == "approved"
await session2.disconnect()
@@ -289,17 +285,13 @@ async def test_one_client_rejects_permission_and_both_see_the_result(
"""One client rejects a permission request and both see the result."""
# Client 1 creates a session and denies all permission requests
session1 = await mctx.client1.create_session(
- on_permission_request=lambda request, invocation: PermissionRequestResult(
- kind="reject"
- ),
+ on_permission_request=lambda request, invocation: PermissionDecisionReject(),
)
# Client 2 observes the permission request but leaves the decision to client 1.
session2 = await mctx.client2.resume_session(
session1.session_id,
- on_permission_request=lambda request, invocation: PermissionRequestResult(
- kind="no-result"
- ),
+ on_permission_request=lambda request, invocation: PermissionNoResult(),
)
client1_events = []
@@ -332,7 +324,7 @@ async def test_one_client_rejects_permission_and_both_see_the_result(
assert len(c1_perm_completed) > 0
assert len(c2_perm_completed) > 0
for event in c1_perm_completed + c2_perm_completed:
- assert event.data.result.kind.value == "denied-interactively-by-user"
+ assert event.data.result.kind == "denied-interactively-by-user"
await session2.disconnect()
@@ -431,10 +423,10 @@ def ephemeral_tool(params: InputParams, invocation: ToolInvocation) -> str:
await asyncio.sleep(0.5)
# Recreate client2 for future tests (but don't rejoin the session)
- actual_port = mctx.client1.actual_port
+ actual_port = mctx.client1.runtime_port
mctx._client2 = CopilotClient(
- ExternalServerConfig(
- url=f"localhost:{actual_port}", tcp_connection_token="py-tcp-shared-test-token"
+ connection=RuntimeConnection.for_uri(
+ f"localhost:{actual_port}", connection_token="py-tcp-shared-test-token"
)
)
diff --git a/python/e2e/test_pending_work_resume_e2e.py b/python/e2e/test_pending_work_resume_e2e.py
index be0e4feec..4b1dfbff8 100644
--- a/python/e2e/test_pending_work_resume_e2e.py
+++ b/python/e2e/test_pending_work_resume_e2e.py
@@ -16,10 +16,13 @@
import pytest
-from copilot import CopilotClient
-from copilot.client import ExternalServerConfig, SubprocessConfig
-from copilot.generated.rpc import HandlePendingToolCallRequest, PermissionDecisionRequest
-from copilot.session import PermissionHandler, PermissionRequestResult
+from copilot import CopilotClient, RuntimeConnection
+from copilot.generated.rpc import (
+ HandlePendingToolCallRequest,
+ PermissionDecisionRequest,
+ PermissionDecisionUserNotAvailable,
+)
+from copilot.session import PermissionHandler
from copilot.tools import Tool, ToolInvocation, ToolResult
from .testharness import E2ETestContext, get_final_assistant_message
@@ -33,15 +36,17 @@ def _make_subprocess_client(ctx: E2ETestContext, *, use_stdio: bool = True) -> C
github_token = (
"fake-token-for-e2e-tests" if os.environ.get("GITHUB_ACTIONS") == "true" else None
)
- return CopilotClient(
- SubprocessConfig(
- cli_path=ctx.cli_path,
- working_directory=ctx.work_dir,
- env=ctx.get_env(),
- github_token=github_token,
- use_stdio=use_stdio,
- tcp_connection_token="py-tcp-shared-test-token",
+ if use_stdio:
+ connection = RuntimeConnection.for_stdio(path=ctx.cli_path)
+ else:
+ connection = RuntimeConnection.for_tcp(
+ path=ctx.cli_path, connection_token="py-tcp-shared-test-token"
)
+ return CopilotClient(
+ connection=connection,
+ working_directory=ctx.work_dir,
+ env=ctx.get_env(),
+ github_token=github_token,
)
@@ -134,7 +139,7 @@ async def test_should_continue_pending_permission_request_after_resume(
server = _make_subprocess_client(ctx, use_stdio=False)
await server.start()
try:
- cli_url = f"localhost:{server.actual_port}"
+ cli_url = f"localhost:{server.runtime_port}"
release_original: asyncio.Future = asyncio.get_event_loop().create_future()
captured_request: asyncio.Future = asyncio.get_event_loop().create_future()
@@ -149,7 +154,9 @@ def original_tool_handler(args):
return f"ORIGINAL_SHOULD_NOT_RUN_{args.get('value', '')}"
suspended_client = CopilotClient(
- ExternalServerConfig(url=cli_url, tcp_connection_token="py-tcp-shared-test-token")
+ connection=RuntimeConnection.for_uri(
+ cli_url, connection_token="py-tcp-shared-test-token"
+ )
)
session1 = await suspended_client.create_session(
on_permission_request=hold_permission,
@@ -175,16 +182,14 @@ def resumed_tool_handler(args):
return f"PERMISSION_RESUMED_{args['value'].upper()}"
resumed_client = CopilotClient(
- ExternalServerConfig(
- url=cli_url, tcp_connection_token="py-tcp-shared-test-token"
+ connection=RuntimeConnection.for_uri(
+ cli_url, connection_token="py-tcp-shared-test-token"
)
)
try:
session2 = await resumed_client.resume_session(
session_id,
- on_permission_request=lambda req, inv: PermissionRequestResult(
- kind="user-not-available"
- ),
+ on_permission_request=lambda req, inv: PermissionDecisionUserNotAvailable(),
continue_pending_work=True,
tools=[_make_pending_tool("resume_permission_tool", resumed_tool_handler)],
)
@@ -212,7 +217,7 @@ def resumed_tool_handler(args):
await _safe_force_stop(resumed_client)
finally:
if not release_original.done():
- release_original.set_result(PermissionRequestResult(kind="user-not-available"))
+ release_original.set_result(PermissionDecisionUserNotAvailable())
finally:
await _safe_force_stop(server)
@@ -222,7 +227,7 @@ async def test_should_continue_pending_external_tool_request_after_resume(
server = _make_subprocess_client(ctx, use_stdio=False)
await server.start()
try:
- cli_url = f"localhost:{server.actual_port}"
+ cli_url = f"localhost:{server.runtime_port}"
tool_started: asyncio.Future = asyncio.get_event_loop().create_future()
release_original: asyncio.Future = asyncio.get_event_loop().create_future()
@@ -234,7 +239,9 @@ async def blocking_external_tool(args):
return await release_original
suspended_client = CopilotClient(
- ExternalServerConfig(url=cli_url, tcp_connection_token="py-tcp-shared-test-token")
+ connection=RuntimeConnection.for_uri(
+ cli_url, connection_token="py-tcp-shared-test-token"
+ )
)
session1 = await suspended_client.create_session(
on_permission_request=PermissionHandler.approve_all,
@@ -255,8 +262,8 @@ async def blocking_external_tool(args):
await suspended_client.force_stop()
resumed_client = CopilotClient(
- ExternalServerConfig(
- url=cli_url, tcp_connection_token="py-tcp-shared-test-token"
+ connection=RuntimeConnection.for_uri(
+ cli_url, connection_token="py-tcp-shared-test-token"
)
)
try:
@@ -294,7 +301,7 @@ async def test_should_continue_parallel_pending_external_tool_requests_after_res
server = _make_subprocess_client(ctx, use_stdio=False)
await server.start()
try:
- cli_url = f"localhost:{server.actual_port}"
+ cli_url = f"localhost:{server.runtime_port}"
tool_a_started: asyncio.Future = asyncio.get_event_loop().create_future()
tool_b_started: asyncio.Future = asyncio.get_event_loop().create_future()
@@ -312,7 +319,9 @@ async def tool_b(args):
return await release_b
suspended_client = CopilotClient(
- ExternalServerConfig(url=cli_url, tcp_connection_token="py-tcp-shared-test-token")
+ connection=RuntimeConnection.for_uri(
+ cli_url, connection_token="py-tcp-shared-test-token"
+ )
)
session1 = await suspended_client.create_session(
on_permission_request=PermissionHandler.approve_all,
@@ -343,8 +352,8 @@ async def tool_b(args):
await suspended_client.force_stop()
resumed_client = CopilotClient(
- ExternalServerConfig(
- url=cli_url, tcp_connection_token="py-tcp-shared-test-token"
+ connection=RuntimeConnection.for_uri(
+ cli_url, connection_token="py-tcp-shared-test-token"
)
)
try:
@@ -386,10 +395,12 @@ async def test_should_resume_successfully_when_no_pending_work_exists(
server = _make_subprocess_client(ctx, use_stdio=False)
await server.start()
try:
- cli_url = f"localhost:{server.actual_port}"
+ cli_url = f"localhost:{server.runtime_port}"
first_client = CopilotClient(
- ExternalServerConfig(url=cli_url, tcp_connection_token="py-tcp-shared-test-token")
+ connection=RuntimeConnection.for_uri(
+ cli_url, connection_token="py-tcp-shared-test-token"
+ )
)
try:
first_session = await first_client.create_session(
@@ -405,7 +416,9 @@ async def test_should_resume_successfully_when_no_pending_work_exists(
await _safe_force_stop(first_client)
resumed_client = CopilotClient(
- ExternalServerConfig(url=cli_url, tcp_connection_token="py-tcp-shared-test-token")
+ connection=RuntimeConnection.for_uri(
+ cli_url, connection_token="py-tcp-shared-test-token"
+ )
)
try:
resumed_session = await resumed_client.resume_session(
@@ -443,10 +456,12 @@ async def blocking_external_tool(args):
server = _make_subprocess_client(ctx, use_stdio=False)
await server.start()
try:
- cli_url = f"localhost:{server.actual_port}"
+ cli_url = f"localhost:{server.runtime_port}"
suspended_client = CopilotClient(
- ExternalServerConfig(url=cli_url, tcp_connection_token="py-tcp-shared-test-token")
+ connection=RuntimeConnection.for_uri(
+ cli_url, connection_token="py-tcp-shared-test-token"
+ )
)
session1 = await suspended_client.create_session(
on_permission_request=PermissionHandler.approve_all,
@@ -467,8 +482,8 @@ async def blocking_external_tool(args):
await suspended_client.force_stop()
resumed_client = CopilotClient(
- ExternalServerConfig(
- url=cli_url, tcp_connection_token="py-tcp-shared-test-token"
+ connection=RuntimeConnection.for_uri(
+ cli_url, connection_token="py-tcp-shared-test-token"
)
)
try:
@@ -479,7 +494,7 @@ async def blocking_external_tool(args):
)
# Verify resume event: continue_pending_work=False and session_was_active=True
- messages = await session2.get_messages()
+ messages = await session2.get_events()
resume_events = [m for m in messages if isinstance(m.data, SessionResumeData)]
assert len(resume_events) == 1, "Expected exactly one session.resume event"
resume_event = resume_events[0]
@@ -518,10 +533,12 @@ async def test_should_report_continuependingwork_true_in_resume_event(
server = _make_subprocess_client(ctx, use_stdio=False)
await server.start()
try:
- cli_url = f"localhost:{server.actual_port}"
+ cli_url = f"localhost:{server.runtime_port}"
first_client = CopilotClient(
- ExternalServerConfig(url=cli_url, tcp_connection_token="py-tcp-shared-test-token")
+ connection=RuntimeConnection.for_uri(
+ cli_url, connection_token="py-tcp-shared-test-token"
+ )
)
try:
first_session = await first_client.create_session(
@@ -538,7 +555,9 @@ async def test_should_report_continuependingwork_true_in_resume_event(
await _safe_force_stop(first_client)
resumed_client = CopilotClient(
- ExternalServerConfig(url=cli_url, tcp_connection_token="py-tcp-shared-test-token")
+ connection=RuntimeConnection.for_uri(
+ cli_url, connection_token="py-tcp-shared-test-token"
+ )
)
try:
resumed_session = await resumed_client.resume_session(
@@ -547,7 +566,7 @@ async def test_should_report_continuependingwork_true_in_resume_event(
continue_pending_work=True,
)
- messages = await resumed_session.get_messages()
+ messages = await resumed_session.get_events()
resume_events = [m for m in messages if isinstance(m.data, SessionResumeData)]
assert len(resume_events) == 1, "Expected exactly one session.resume event"
resume_event = resume_events[0]
diff --git a/python/e2e/test_per_session_auth_e2e.py b/python/e2e/test_per_session_auth_e2e.py
index b03945deb..0aa42cdaa 100644
--- a/python/e2e/test_per_session_auth_e2e.py
+++ b/python/e2e/test_per_session_auth_e2e.py
@@ -2,7 +2,7 @@
import pytest
-from copilot.client import CopilotClient, SubprocessConfig
+from copilot.client import CopilotClient, RuntimeConnection
from copilot.session import PermissionHandler
from .testharness import E2ETestContext
@@ -18,7 +18,7 @@ async def auth_ctx(ctx: E2ETestContext):
# Redirect GitHub API calls to the proxy so per-session auth token
# resolution (fetchCopilotUser) is intercepted. Must be set before the
# CLI subprocess is spawned (i.e., before the first create_session call).
- ctx.client._config.env["COPILOT_DEBUG_GITHUB_API_URL"] = proxy_url
+ ctx.client._options.env["COPILOT_DEBUG_GITHUB_API_URL"] = proxy_url
await ctx.set_copilot_user_by_token(
"token-alice",
@@ -97,12 +97,10 @@ async def test_should_return_unauthenticated_when_no_token_provided(
env = without_auth_env(auth_ctx.get_env())
env["COPILOT_DEBUG_GITHUB_API_URL"] = auth_ctx.proxy_url
no_token_client = CopilotClient(
- SubprocessConfig(
- cli_path=auth_ctx.cli_path,
- working_directory=auth_ctx.work_dir,
- env=env,
- use_logged_in_user=False,
- )
+ connection=RuntimeConnection.for_stdio(path=auth_ctx.cli_path),
+ working_directory=auth_ctx.work_dir,
+ env=env,
+ use_logged_in_user=False,
)
try:
diff --git a/python/e2e/test_permissions_e2e.py b/python/e2e/test_permissions_e2e.py
index 46cf2f3d4..dbea6d384 100644
--- a/python/e2e/test_permissions_e2e.py
+++ b/python/e2e/test_permissions_e2e.py
@@ -6,12 +6,17 @@
import pytest
+from copilot.generated.rpc import (
+ PermissionDecisionApproveOnce,
+ PermissionDecisionReject,
+ PermissionDecisionUserNotAvailable,
+)
from copilot.generated.session_events import (
PermissionRequest,
SessionIdleData,
ToolExecutionCompleteData,
)
-from copilot.session import PermissionHandler, PermissionRequestResult
+from copilot.session import PermissionHandler, PermissionNoResult, PermissionRequestResult
from .testharness import E2ETestContext
from .testharness.helper import read_file, write_file
@@ -29,7 +34,7 @@ def on_permission_request(
) -> PermissionRequestResult:
permission_requests.append(request)
assert invocation["session_id"] == session.session_id
- return PermissionRequestResult(kind="approve-once")
+ return PermissionDecisionApproveOnce()
session = await ctx.client.create_session(on_permission_request=on_permission_request)
@@ -41,7 +46,7 @@ def on_permission_request(
assert len(permission_requests) > 0
# Should include write permission request
- write_requests = [req for req in permission_requests if req.kind.value == "write"]
+ write_requests = [req for req in permission_requests if req.kind == "write"]
assert len(write_requests) > 0
await session.disconnect()
@@ -52,7 +57,7 @@ async def test_should_deny_permission_when_handler_returns_denied(self, ctx: E2E
def on_permission_request(
request: PermissionRequest, invocation: dict
) -> PermissionRequestResult:
- return PermissionRequestResult(kind="reject")
+ return PermissionDecisionReject()
session = await ctx.client.create_session(on_permission_request=on_permission_request)
@@ -97,7 +102,7 @@ async def test_should_deny_tool_operations_when_handler_explicitly_denies(
"""Test that tool operations are denied when handler explicitly denies"""
def deny_all(request, invocation):
- return PermissionRequestResult()
+ return PermissionDecisionUserNotAvailable()
session = await ctx.client.create_session(on_permission_request=deny_all)
@@ -138,7 +143,7 @@ async def test_should_deny_tool_operations_when_handler_explicitly_denies_after_
await session1.send_and_wait("What is 1+1?")
def deny_all(request, invocation):
- return PermissionRequestResult()
+ return PermissionDecisionUserNotAvailable()
session2 = await ctx.client.resume_session(session_id, on_permission_request=deny_all)
@@ -190,7 +195,7 @@ async def on_permission_request(
) -> PermissionRequestResult:
permission_requests.append(request)
await asyncio.sleep(0)
- return PermissionRequestResult(kind="approve-once")
+ return PermissionDecisionApproveOnce()
session = await ctx.client.create_session(on_permission_request=on_permission_request)
@@ -216,7 +221,7 @@ def on_permission_request(
request: PermissionRequest, invocation: dict
) -> PermissionRequestResult:
permission_requests.append(request)
- return PermissionRequestResult(kind="approve-once")
+ return PermissionDecisionApproveOnce()
session2 = await ctx.client.resume_session(
session_id, on_permission_request=on_permission_request
@@ -260,7 +265,7 @@ def on_permission_request(
received_tool_call_id = True
assert isinstance(request.tool_call_id, str)
assert len(request.tool_call_id) > 0
- return PermissionRequestResult(kind="approve-once")
+ return PermissionDecisionApproveOnce()
session = await ctx.client.create_session(on_permission_request=on_permission_request)
@@ -289,7 +294,7 @@ async def slow_permission(request: PermissionRequest, invocation: dict):
handler_entered.set_result(True)
await asyncio.wait_for(release_handler, timeout=30.0)
add_event("permission-complete", tool_call_id)
- return PermissionRequestResult(kind="approve-once")
+ return PermissionDecisionApproveOnce()
session = await ctx.client.create_session(on_permission_request=slow_permission)
@@ -376,7 +381,7 @@ async def test_should_deny_permission_with_noresult_kind(self, ctx: E2ETestConte
def deny_noresult(request: PermissionRequest, invocation: dict) -> PermissionRequestResult:
if not permission_called.done():
permission_called.set_result(True)
- return PermissionRequestResult(kind="no-result")
+ return PermissionNoResult()
session = await ctx.client.create_session(on_permission_request=deny_noresult)
try:
@@ -399,7 +404,7 @@ def counting_handler(
) -> PermissionRequestResult:
nonlocal handler_call_count
handler_call_count += 1
- return PermissionRequestResult(kind="approve-once")
+ return PermissionDecisionApproveOnce()
session = await ctx.client.create_session(on_permission_request=counting_handler)
try:
@@ -458,7 +463,7 @@ async def concurrent_permission(request: PermissionRequest, invocation: dict):
if permission_request_count >= 2 and not both_started.done():
both_started.set_result(True)
await asyncio.wait_for(both_started, timeout=30.0)
- return PermissionRequestResult(kind="approve-once")
+ return PermissionDecisionApproveOnce()
def first_tool_handler(invocation: ToolInvocation) -> ToolResult:
nonlocal first_tool_called
diff --git a/python/e2e/test_pre_mcp_tool_call_hook_e2e.py b/python/e2e/test_pre_mcp_tool_call_hook_e2e.py
index 9a140dd38..c59994437 100644
--- a/python/e2e/test_pre_mcp_tool_call_hook_e2e.py
+++ b/python/e2e/test_pre_mcp_tool_call_hook_e2e.py
@@ -5,6 +5,7 @@
from __future__ import annotations
+from datetime import datetime
from pathlib import Path
import pytest
@@ -58,7 +59,7 @@ async def on_pre_mcp_tool_call(input_data, invocation):
assert inputs[0].get("serverName") == "meta-echo"
assert inputs[0].get("toolName") == "echo_meta"
assert inputs[0].get("workingDirectory")
- assert inputs[0].get("timestamp", 0) > 0
+ assert isinstance(inputs[0].get("timestamp"), datetime)
finally:
await session.disconnect()
diff --git a/python/e2e/test_rpc_e2e.py b/python/e2e/test_rpc_e2e.py
index 511b9d1d1..b825db060 100644
--- a/python/e2e/test_rpc_e2e.py
+++ b/python/e2e/test_rpc_e2e.py
@@ -2,8 +2,7 @@
import pytest
-from copilot import CopilotClient
-from copilot.client import SubprocessConfig
+from copilot import CopilotClient, RuntimeConnection
from copilot.generated.rpc import ModelsListRequest, PingRequest
from copilot.session import PermissionHandler
@@ -16,7 +15,7 @@ class TestRpc:
@pytest.mark.asyncio
async def test_should_call_rpc_ping_with_typed_params(self):
"""Test calling rpc.ping with typed params and result"""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
try:
await client.start()
@@ -32,7 +31,7 @@ async def test_should_call_rpc_ping_with_typed_params(self):
@pytest.mark.asyncio
async def test_should_call_rpc_models_list(self):
"""Test calling rpc.models.list with typed result"""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
try:
await client.start()
@@ -55,7 +54,7 @@ async def test_should_call_rpc_models_list(self):
@pytest.mark.asyncio
async def test_should_call_rpc_account_get_quota(self):
"""Test calling rpc.account.getQuota when authenticated"""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
try:
await client.start()
@@ -116,7 +115,7 @@ async def test_get_and_set_session_mode(self):
"""Test getting and setting session mode"""
from copilot.generated.rpc import ModeSetRequest, SessionMode
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
try:
await client.start()
@@ -148,7 +147,7 @@ async def test_read_update_and_delete_plan(self):
"""Test reading, updating, and deleting plan"""
from copilot.generated.rpc import PlanUpdateRequest
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
try:
await client.start()
@@ -191,7 +190,7 @@ async def test_create_list_and_read_workspace_files(self):
WorkspacesReadFileRequest,
)
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
try:
await client.start()
diff --git a/python/e2e/test_rpc_event_side_effects_e2e.py b/python/e2e/test_rpc_event_side_effects_e2e.py
index b4a5b2790..9725e211a 100644
--- a/python/e2e/test_rpc_event_side_effects_e2e.py
+++ b/python/e2e/test_rpc_event_side_effects_e2e.py
@@ -215,7 +215,7 @@ async def test_should_emit_snapshot_rewind_event_and_remove_events_on_truncate(
try:
await session.send_and_wait("Say SNAPSHOT_REWIND_TARGET exactly.", timeout=60.0)
- events = await session.get_messages()
+ events = await session.get_events()
user_msgs = [e for e in events if isinstance(e.data, UserMessageData)]
assert len(user_msgs) >= 1
first_user_event_id = str(user_msgs[0].id)
@@ -236,7 +236,7 @@ def on_event(event):
assert evt.data.events_removed >= 1
assert evt.data.up_to_event_id.lower() == first_user_event_id.lower()
- messages_after = await session.get_messages()
+ messages_after = await session.get_events()
assert not any(e.id == user_msgs[0].id for e in messages_after)
except Exception as exc:
if "unhandled method" in str(exc).lower():
@@ -257,7 +257,7 @@ async def test_should_allow_session_use_after_truncate(self, ctx: E2ETestContext
try:
await session.send_and_wait("Say SNAPSHOT_REWIND_TARGET exactly.", timeout=60.0)
- events = await session.get_messages()
+ events = await session.get_events()
user_msgs = [e for e in events if isinstance(e.data, UserMessageData)]
assert len(user_msgs) >= 1
first_user_event_id = str(user_msgs[0].id)
diff --git a/python/e2e/test_rpc_server_e2e.py b/python/e2e/test_rpc_server_e2e.py
index f5dc9920d..481e50d7b 100644
--- a/python/e2e/test_rpc_server_e2e.py
+++ b/python/e2e/test_rpc_server_e2e.py
@@ -12,8 +12,7 @@
import pytest
-from copilot import CopilotClient
-from copilot.client import SubprocessConfig
+from copilot import CopilotClient, RuntimeConnection
from copilot.generated.rpc import (
AccountGetQuotaRequest,
MCPDiscoverRequest,
@@ -48,7 +47,7 @@ def _create_skill_directory(work_dir: str, skill_name: str, description: str) ->
@pytest.fixture(scope="module")
async def authed_ctx(ctx: E2ETestContext):
"""Configure proxy to redirect GitHub user lookups so per-token auth works."""
- ctx.client._config.env["COPILOT_DEBUG_GITHUB_API_URL"] = ctx.proxy_url
+ ctx.client._options.env["COPILOT_DEBUG_GITHUB_API_URL"] = ctx.proxy_url
return ctx
@@ -56,12 +55,10 @@ def _make_authed_client(ctx: E2ETestContext, token: str) -> CopilotClient:
env = ctx.get_env()
env["COPILOT_DEBUG_GITHUB_API_URL"] = ctx.proxy_url
return CopilotClient(
- SubprocessConfig(
- cli_path=ctx.cli_path,
- working_directory=ctx.work_dir,
- env=env,
- github_token=token,
- )
+ connection=RuntimeConnection.for_stdio(path=ctx.cli_path),
+ working_directory=ctx.work_dir,
+ env=env,
+ github_token=token,
)
diff --git a/python/e2e/test_rpc_session_state_e2e.py b/python/e2e/test_rpc_session_state_e2e.py
index b7329158c..f5b11f6fa 100644
--- a/python/e2e/test_rpc_session_state_e2e.py
+++ b/python/e2e/test_rpc_session_state_e2e.py
@@ -171,7 +171,7 @@ async def test_should_fork_session_with_persisted_messages(self, ctx: E2ETestCon
assert initial_answer is not None
assert "FORK_SOURCE_ALPHA" in (initial_answer.data.content or "")
- source_messages = await session.get_messages()
+ source_messages = await session.get_events()
source_conversation = _conversation_messages(source_messages)
assert any(
role == "user" and content == source_prompt for role, content in source_conversation
@@ -192,7 +192,7 @@ async def test_should_fork_session_with_persisted_messages(self, ctx: E2ETestCon
on_permission_request=PermissionHandler.approve_all,
)
try:
- forked_messages = await forked_session.get_messages()
+ forked_messages = await forked_session.get_events()
forked_conversation = _conversation_messages(forked_messages)
assert forked_conversation[: len(source_conversation)] == source_conversation
@@ -200,10 +200,10 @@ async def test_should_fork_session_with_persisted_messages(self, ctx: E2ETestCon
assert fork_answer is not None
assert "FORK_CHILD_BETA" in (fork_answer.data.content or "")
- source_after_fork = _conversation_messages(await session.get_messages())
+ source_after_fork = _conversation_messages(await session.get_events())
assert all(content != fork_prompt for _, content in source_after_fork)
- fork_after_prompt = _conversation_messages(await forked_session.get_messages())
+ fork_after_prompt = _conversation_messages(await forked_session.get_events())
assert any(
role == "user" and content == fork_prompt for role, content in fork_after_prompt
)
@@ -241,7 +241,7 @@ async def test_should_handle_forking_session_without_persisted_events(
on_permission_request=PermissionHandler.approve_all,
)
try:
- assert _conversation_messages(await forked_session.get_messages()) == []
+ assert _conversation_messages(await forked_session.get_events()) == []
finally:
await forked_session.disconnect()
finally:
@@ -506,7 +506,7 @@ async def test_should_fork_session_to_event_id_excluding_boundary_event(
await session.send_and_wait(first_prompt, timeout=60.0)
await session.send_and_wait(second_prompt, timeout=60.0)
- source_events = await session.get_messages()
+ source_events = await session.get_events()
second_user_event = next(
(
e
@@ -531,7 +531,7 @@ async def test_should_fork_session_to_event_id_excluding_boundary_event(
on_permission_request=PermissionHandler.approve_all,
)
try:
- forked_events = await forked_session.get_messages()
+ forked_events = await forked_session.get_events()
forked_ids = {str(e.id) for e in forked_events}
assert boundary_event_id not in forked_ids, (
"toEventId is exclusive — boundary event must not be in forked session"
diff --git a/python/e2e/test_rpc_shell_and_fleet_e2e.py b/python/e2e/test_rpc_shell_and_fleet_e2e.py
index c5384825b..32177cbbd 100644
--- a/python/e2e/test_rpc_shell_and_fleet_e2e.py
+++ b/python/e2e/test_rpc_shell_and_fleet_e2e.py
@@ -128,7 +128,7 @@ def record_fleet_completion(invocation: ToolInvocation) -> ToolResult:
async def _wait_for_messages(timeout: float = 120.0):
deadline = asyncio.get_event_loop().time() + timeout
while asyncio.get_event_loop().time() < deadline:
- messages = await session.get_messages()
+ messages = await session.get_events()
if any(
isinstance(m.data, AssistantMessageData)
and "fleet task" in (m.data.content or "").lower()
diff --git a/python/e2e/test_rpc_tasks_and_handlers_e2e.py b/python/e2e/test_rpc_tasks_and_handlers_e2e.py
index 707c8b781..23b9f9896 100644
--- a/python/e2e/test_rpc_tasks_and_handlers_e2e.py
+++ b/python/e2e/test_rpc_tasks_and_handlers_e2e.py
@@ -12,14 +12,15 @@
import pytest
from copilot.generated.rpc import (
- ApprovalKind,
CommandsHandlePendingCommandRequest,
HandlePendingToolCallRequest,
- PermissionDecision,
- PermissionDecisionApproveForIonApproval,
- PermissionDecisionKind,
+ PermissionDecisionApproveForLocation,
+ PermissionDecisionApproveForLocationApprovalCustomTool,
+ PermissionDecisionApproveForSession,
+ PermissionDecisionApproveForSessionApprovalCustomTool,
+ PermissionDecisionApprovePermanently,
+ PermissionDecisionReject,
PermissionDecisionRequest,
- TaskInfoType,
TasksCancelRequest,
TasksPromoteToBackgroundRequest,
TasksRemoveRequest,
@@ -137,10 +138,7 @@ async def test_should_return_expected_results_for_missing_pending_handler_reques
permission = await session.rpc.permissions.handle_pending_permission_request(
PermissionDecisionRequest(
request_id="missing-permission-request",
- result=PermissionDecision(
- kind=PermissionDecisionKind.REJECT,
- feedback="not approved",
- ),
+ result=PermissionDecisionReject(feedback="not approved"),
)
)
assert permission.success is False
@@ -148,10 +146,7 @@ async def test_should_return_expected_results_for_missing_pending_handler_reques
permanent = await session.rpc.permissions.handle_pending_permission_request(
PermissionDecisionRequest(
request_id="missing-permanent-permission-request",
- result=PermissionDecision(
- kind=PermissionDecisionKind.APPROVE_PERMANENTLY,
- domain="example.com",
- ),
+ result=PermissionDecisionApprovePermanently(domain="example.com"),
)
)
assert permanent.success is False
@@ -159,10 +154,8 @@ async def test_should_return_expected_results_for_missing_pending_handler_reques
session_approval = await session.rpc.permissions.handle_pending_permission_request(
PermissionDecisionRequest(
request_id="missing-session-approval-request",
- result=PermissionDecision(
- kind=PermissionDecisionKind.APPROVE_FOR_SESSION,
- approval=PermissionDecisionApproveForIonApproval(
- kind=ApprovalKind.CUSTOM_TOOL,
+ result=PermissionDecisionApproveForSession(
+ approval=PermissionDecisionApproveForSessionApprovalCustomTool(
tool_name="missing-tool",
),
),
@@ -173,11 +166,9 @@ async def test_should_return_expected_results_for_missing_pending_handler_reques
location_approval = await session.rpc.permissions.handle_pending_permission_request(
PermissionDecisionRequest(
request_id="missing-location-approval-request",
- result=PermissionDecision(
- kind=PermissionDecisionKind.APPROVE_FOR_LOCATION,
+ result=PermissionDecisionApproveForLocation(
location_key="missing-location",
- approval=PermissionDecisionApproveForIonApproval(
- kind=ApprovalKind.CUSTOM_TOOL,
+ approval=PermissionDecisionApproveForLocationApprovalCustomTool(
tool_name="missing-tool",
),
),
@@ -215,7 +206,7 @@ async def test_should_report_implemented_error_for_invalid_task_agent_model(
async def test_should_start_background_agent_and_report_task_details(self, ctx: E2ETestContext):
"""Start a background agent task and verify task details then remove it."""
- from copilot.generated.rpc import TaskInfoExecutionMode, TaskInfoStatus
+ from copilot.generated.rpc import TaskAgentInfo, TaskInfoExecutionMode, TaskInfoStatus
session = await ctx.client.create_session(
on_permission_request=PermissionHandler.approve_all,
@@ -248,7 +239,7 @@ async def test_should_start_background_agent_and_report_task_details(self, ctx:
)
assert found_task.id == task_id
assert found_task.description == "SDK background agent coverage"
- assert found_task.type == TaskInfoType.AGENT
+ assert isinstance(found_task, TaskAgentInfo)
assert found_task.agent_type == "general-purpose"
assert found_task.execution_mode == TaskInfoExecutionMode.BACKGROUND
assert found_task.prompt == "Reply with TASK_AGENT_DONE exactly."
diff --git a/python/e2e/test_session_config_e2e.py b/python/e2e/test_session_config_e2e.py
index 1fd2cd0a2..b018ba6f8 100644
--- a/python/e2e/test_session_config_e2e.py
+++ b/python/e2e/test_session_config_e2e.py
@@ -171,7 +171,7 @@ async def test_should_use_custom_sessionid(self, ctx: E2ETestContext):
)
assert session.session_id == requested_session_id
- messages = await session.get_messages()
+ messages = await session.get_events()
assert messages
start_event = messages[0]
assert isinstance(start_event.data, SessionStartData)
diff --git a/python/e2e/test_session_e2e.py b/python/e2e/test_session_e2e.py
index d5a0c970e..db9b61e24 100644
--- a/python/e2e/test_session_e2e.py
+++ b/python/e2e/test_session_e2e.py
@@ -2,11 +2,11 @@
import base64
import os
+from datetime import datetime
import pytest
-from copilot import CopilotClient
-from copilot.client import SubprocessConfig
+from copilot import CopilotClient, RuntimeConnection
from copilot.generated.session_events import SessionModelChangeData
from copilot.session import PermissionHandler
from copilot.tools import Tool, ToolResult
@@ -23,7 +23,7 @@ async def test_should_create_and_disconnect_sessions(self, ctx: E2ETestContext):
)
assert session.session_id
- messages = await session.get_messages()
+ messages = await session.get_events()
assert len(messages) > 0
assert messages[0].type.value == "session.start"
assert messages[0].data.session_id == session.session_id
@@ -32,7 +32,7 @@ async def test_should_create_and_disconnect_sessions(self, ctx: E2ETestContext):
await session.disconnect()
with pytest.raises(Exception, match="Session not found"):
- await session.get_messages()
+ await session.get_events()
async def test_should_have_stateful_conversation(self, ctx: E2ETestContext):
session = await ctx.client.create_session(
@@ -194,7 +194,7 @@ async def test_should_handle_multiple_concurrent_sessions(self, ctx: E2ETestCont
# All are connected
for s in [s1, s2, s3]:
- messages = await s.get_messages()
+ messages = await s.get_events()
assert len(messages) > 0
assert messages[0].type.value == "session.start"
assert messages[0].data.session_id == s.session_id
@@ -203,7 +203,7 @@ async def test_should_handle_multiple_concurrent_sessions(self, ctx: E2ETestCont
await asyncio.gather(s1.disconnect(), s2.disconnect(), s3.disconnect())
for s in [s1, s2, s3]:
with pytest.raises(Exception, match="Session not found"):
- await s.get_messages()
+ await s.get_events()
async def test_should_resume_a_session_using_the_same_client(self, ctx: E2ETestContext):
# Create initial session
@@ -243,12 +243,10 @@ async def test_should_resume_a_session_using_a_new_client(self, ctx: E2ETestCont
"fake-token-for-e2e-tests" if os.environ.get("GITHUB_ACTIONS") == "true" else None
)
new_client = CopilotClient(
- SubprocessConfig(
- cli_path=ctx.cli_path,
- working_directory=ctx.work_dir,
- env=ctx.get_env(),
- github_token=github_token,
- )
+ connection=RuntimeConnection.for_stdio(path=ctx.cli_path),
+ working_directory=ctx.work_dir,
+ env=ctx.get_env(),
+ github_token=github_token,
)
try:
@@ -257,7 +255,7 @@ async def test_should_resume_a_session_using_a_new_client(self, ctx: E2ETestCont
)
assert session2.session_id == session_id
- messages = await session2.get_messages()
+ messages = await session2.get_events()
message_types = [m.type.value for m in messages]
assert "user.message" in message_types
assert "session.resume" in message_types
@@ -295,21 +293,21 @@ async def test_should_list_sessions(self, ctx: E2ETestContext):
sessions = await ctx.client.list_sessions()
assert isinstance(sessions, list)
- session_ids = [s.sessionId for s in sessions]
+ session_ids = [s.session_id for s in sessions]
assert session1.session_id in session_ids
assert session2.session_id in session_ids
# Verify session metadata structure
for session_data in sessions:
- assert hasattr(session_data, "sessionId")
- assert hasattr(session_data, "startTime")
- assert hasattr(session_data, "modifiedTime")
- assert hasattr(session_data, "isRemote")
+ assert hasattr(session_data, "session_id")
+ assert hasattr(session_data, "start_time")
+ assert hasattr(session_data, "modified_time")
+ assert hasattr(session_data, "is_remote")
# summary is optional
- assert isinstance(session_data.sessionId, str)
- assert isinstance(session_data.startTime, str)
- assert isinstance(session_data.modifiedTime, str)
- assert isinstance(session_data.isRemote, bool)
+ assert isinstance(session_data.session_id, str)
+ assert isinstance(session_data.start_time, datetime)
+ assert isinstance(session_data.modified_time, datetime)
+ assert isinstance(session_data.is_remote, bool)
# Verify context field is present
for session_data in sessions:
@@ -333,7 +331,7 @@ async def test_should_delete_session(self, ctx: E2ETestContext):
# Verify session exists in the list
sessions = await ctx.client.list_sessions()
- session_ids = [s.sessionId for s in sessions]
+ session_ids = [s.session_id for s in sessions]
assert session_id in session_ids
# Delete the session
@@ -341,7 +339,7 @@ async def test_should_delete_session(self, ctx: E2ETestContext):
# Verify session no longer exists in the list
sessions_after = await ctx.client.list_sessions()
- session_ids_after = [s.sessionId for s in sessions_after]
+ session_ids_after = [s.session_id for s in sessions_after]
assert session_id not in session_ids_after
# Verify we cannot resume the deleted session
@@ -365,10 +363,10 @@ async def test_should_get_session_metadata(self, ctx: E2ETestContext):
# Get metadata for the session we just created
metadata = await ctx.client.get_session_metadata(session.session_id)
assert metadata is not None
- assert metadata.sessionId == session.session_id
- assert isinstance(metadata.startTime, str)
- assert isinstance(metadata.modifiedTime, str)
- assert isinstance(metadata.isRemote, bool)
+ assert metadata.session_id == session.session_id
+ assert isinstance(metadata.start_time, datetime)
+ assert isinstance(metadata.modified_time, datetime)
+ assert isinstance(metadata.is_remote, bool)
# Verify context field is present
if metadata.context is not None:
@@ -499,7 +497,7 @@ async def test_should_abort_a_session(self, ctx: E2ETestContext):
_ = await wait_for_session_idle
# The session should still be alive and usable after abort
- messages = await session.get_messages()
+ messages = await session.get_events()
assert len(messages) > 0
# Verify an abort event exists in messages
@@ -555,7 +553,7 @@ def on_event(event):
assert "session.idle" in event_types
# Verify the assistant response contains the expected answer.
- # session.idle is ephemeral and not in get_messages(), but we already
+ # session.idle is ephemeral and not in get_events(), but we already
# confirmed idle via the live event handler above.
assistant_message = await get_final_assistant_message(session, already_idle=True)
assert "300" in assistant_message.data.content
@@ -696,13 +694,13 @@ async def test_should_send_with_file_attachment(self, ctx: E2ETestContext):
],
)
- messages = await session.get_messages()
+ messages = await session.get_events()
user_messages = [m for m in messages if isinstance(m.data, UserMessageData)]
assert user_messages
attachments = user_messages[-1].data.attachments
assert attachments is not None and len(attachments) == 1
attachment = attachments[0]
- assert attachment.type.value == "file"
+ assert attachment.type == "file"
assert attachment.display_name == "attached-file.txt"
assert attachment.path == file_path
assert attachment.line_range is not None
@@ -734,13 +732,13 @@ async def test_should_send_with_directory_attachment(self, ctx: E2ETestContext):
],
)
- messages = await session.get_messages()
+ messages = await session.get_events()
user_messages = [m for m in messages if isinstance(m.data, UserMessageData)]
assert user_messages
attachments = user_messages[-1].data.attachments
assert attachments is not None and len(attachments) == 1
attachment = attachments[0]
- assert attachment.type.value == "directory"
+ assert attachment.type == "directory"
assert attachment.display_name == "attached-directory"
assert attachment.path == directory_path
@@ -773,13 +771,13 @@ async def test_should_send_with_selection_attachment(self, ctx: E2ETestContext):
],
)
- messages = await session.get_messages()
+ messages = await session.get_events()
user_messages = [m for m in messages if isinstance(m.data, UserMessageData)]
assert user_messages
attachments = user_messages[-1].data.attachments
assert attachments is not None and len(attachments) == 1
attachment = attachments[0]
- assert attachment.type.value == "selection"
+ assert attachment.type == "selection"
assert attachment.display_name == "selected-file.cs"
assert attachment.file_path == file_path
assert attachment.text == 'string Value = "SELECTION_SENTINEL";'
@@ -822,7 +820,7 @@ async def test_should_list_sessions_with_context(self, ctx: E2ETestContext):
our_session = None
for _ in range(50):
sessions = await ctx.client.list_sessions()
- our_session = next((s for s in sessions if s.sessionId == session.session_id), None)
+ our_session = next((s for s in sessions if s.session_id == session.session_id), None)
if our_session is not None:
break
await asyncio.sleep(0.1)
@@ -854,9 +852,9 @@ async def test_should_get_session_metadata_by_id(self, ctx: E2ETestContext):
break
await asyncio.sleep(0.1)
assert metadata is not None
- assert metadata.sessionId == session.session_id
- assert isinstance(metadata.startTime, str) and metadata.startTime
- assert isinstance(metadata.modifiedTime, str) and metadata.modifiedTime
+ assert metadata.session_id == session.session_id
+ assert isinstance(metadata.start_time, datetime)
+ assert isinstance(metadata.modified_time, datetime)
not_found = await ctx.client.get_session_metadata("non-existent-session-id")
assert not_found is None
@@ -1085,7 +1083,7 @@ async def test_should_send_with_mode_property(self, ctx: E2ETestContext):
mode="plan", # type: ignore[arg-type]
)
- messages = await session.get_messages()
+ messages = await session.get_events()
user_messages = [m for m in messages if isinstance(m.data, UserMessageData)]
assert user_messages
last = user_messages[-1].data
diff --git a/python/e2e/test_session_fs_e2e.py b/python/e2e/test_session_fs_e2e.py
index 0afb565ef..9d00057ec 100644
--- a/python/e2e/test_session_fs_e2e.py
+++ b/python/e2e/test_session_fs_e2e.py
@@ -12,8 +12,12 @@
import pytest
import pytest_asyncio
-from copilot import CopilotClient, SessionFsConfig, define_tool
-from copilot.client import ExternalServerConfig, SubprocessConfig
+from copilot import (
+ CopilotClient,
+ RuntimeConnection,
+ SessionFsConfig,
+ define_tool,
+)
from copilot.generated.rpc import (
SessionFSReaddirWithTypesEntry,
SessionFSReaddirWithTypesEntryType,
@@ -45,13 +49,11 @@
@pytest_asyncio.fixture(scope="module", loop_scope="module")
async def session_fs_client(ctx: E2ETestContext):
client = CopilotClient(
- SubprocessConfig(
- cli_path=ctx.cli_path,
- working_directory=ctx.work_dir,
- env=ctx.get_env(),
- github_token=DEFAULT_GITHUB_TOKEN,
- session_fs=SESSION_FS_CONFIG,
- )
+ connection=RuntimeConnection.for_stdio(path=ctx.cli_path),
+ working_directory=ctx.work_dir,
+ env=ctx.get_env(),
+ github_token=DEFAULT_GITHUB_TOKEN,
+ session_fs=SESSION_FS_CONFIG,
)
yield client
try:
@@ -117,13 +119,10 @@ async def test_should_load_session_data_from_fs_provider_on_resume(
async def test_should_reject_setprovider_when_sessions_already_exist(self, ctx: E2ETestContext):
client1 = CopilotClient(
- SubprocessConfig(
- cli_path=ctx.cli_path,
- working_directory=ctx.work_dir,
- env=ctx.get_env(),
- use_stdio=False,
- github_token=DEFAULT_GITHUB_TOKEN,
- )
+ connection=RuntimeConnection.for_tcp(path=ctx.cli_path),
+ working_directory=ctx.work_dir,
+ env=ctx.get_env(),
+ github_token=DEFAULT_GITHUB_TOKEN,
)
session = None
client2 = None
@@ -132,14 +131,12 @@ async def test_should_reject_setprovider_when_sessions_already_exist(self, ctx:
session = await client1.create_session(
on_permission_request=PermissionHandler.approve_all,
)
- actual_port = client1.actual_port
+ actual_port = client1.runtime_port
assert actual_port is not None
client2 = CopilotClient(
- ExternalServerConfig(
- url=f"localhost:{actual_port}",
- session_fs=SESSION_FS_CONFIG,
- )
+ connection=RuntimeConnection.for_uri(f"localhost:{actual_port}"),
+ session_fs=SESSION_FS_CONFIG,
)
with pytest.raises(Exception):
@@ -171,7 +168,7 @@ def get_big_string() -> str:
"Call the get_big_string tool and reply with the word DONE only."
)
- messages = await session.get_messages()
+ messages = await session.get_events()
tool_result = find_tool_call_result(messages, "get_big_string")
assert tool_result is not None
assert f"{SESSION_STATE_PATH}/temp/" in tool_result
diff --git a/python/e2e/test_session_fs_sqlite_e2e.py b/python/e2e/test_session_fs_sqlite_e2e.py
index 38c15ae08..565c55336 100644
--- a/python/e2e/test_session_fs_sqlite_e2e.py
+++ b/python/e2e/test_session_fs_sqlite_e2e.py
@@ -12,8 +12,7 @@
import pytest
import pytest_asyncio
-from copilot import CopilotClient, SessionFsConfig
-from copilot.client import SubprocessConfig
+from copilot import CopilotClient, RuntimeConnection, SessionFsConfig
from copilot.generated.rpc import (
SessionFSReaddirWithTypesEntry,
SessionFSReaddirWithTypesEntryType,
@@ -200,13 +199,11 @@ def factory(session):
@pytest_asyncio.fixture(scope="module", loop_scope="module")
async def sqlite_client(ctx: E2ETestContext):
client = CopilotClient(
- SubprocessConfig(
- cli_path=ctx.cli_path,
- working_directory=ctx.work_dir,
- env=ctx.get_env(),
- github_token=DEFAULT_GITHUB_TOKEN,
- session_fs=SESSION_FS_CONFIG,
- )
+ connection=RuntimeConnection.for_stdio(path=ctx.cli_path),
+ working_directory=ctx.work_dir,
+ env=ctx.get_env(),
+ github_token=DEFAULT_GITHUB_TOKEN,
+ session_fs=SESSION_FS_CONFIG,
)
yield client
try:
diff --git a/python/e2e/test_streaming_fidelity_e2e.py b/python/e2e/test_streaming_fidelity_e2e.py
index e47fb9911..79b34fc91 100644
--- a/python/e2e/test_streaming_fidelity_e2e.py
+++ b/python/e2e/test_streaming_fidelity_e2e.py
@@ -4,8 +4,7 @@
import pytest
-from copilot import CopilotClient
-from copilot.client import SubprocessConfig
+from copilot import CopilotClient, RuntimeConnection
from copilot.session import PermissionHandler
from .testharness import E2ETestContext
@@ -79,12 +78,10 @@ async def test_should_produce_deltas_after_session_resume(self, ctx: E2ETestCont
"fake-token-for-e2e-tests" if os.environ.get("GITHUB_ACTIONS") == "true" else None
)
new_client = CopilotClient(
- SubprocessConfig(
- cli_path=ctx.cli_path,
- working_directory=ctx.work_dir,
- env=ctx.get_env(),
- github_token=github_token,
- )
+ connection=RuntimeConnection.for_stdio(path=ctx.cli_path),
+ working_directory=ctx.work_dir,
+ env=ctx.get_env(),
+ github_token=github_token,
)
try:
@@ -131,12 +128,10 @@ async def test_should_not_produce_deltas_after_session_resume_with_streaming_dis
# Resume with streaming disabled
new_client = CopilotClient(
- SubprocessConfig(
- cli_path=ctx.cli_path,
- working_directory=ctx.work_dir,
- env=ctx.get_env(),
- github_token=github_token,
- )
+ connection=RuntimeConnection.for_stdio(path=ctx.cli_path),
+ working_directory=ctx.work_dir,
+ env=ctx.get_env(),
+ github_token=github_token,
)
try:
session2 = await new_client.resume_session(
@@ -184,8 +179,8 @@ async def test_should_emit_streaming_deltas_with_reasoning_effort_configured(
assistant_events = [e for e in events if e.type.value == "assistant.message"]
assert len(assistant_events) >= 1, "Expected final assistant.message"
- # Check session.start event (from get_messages) has reasoning_effort
- all_msgs = await session.get_messages()
+ # Check session.start event (from get_events) has reasoning_effort
+ all_msgs = await session.get_events()
start_event = next((e for e in all_msgs if isinstance(e.data, SessionStartData)), None)
assert start_event is not None, "Expected session.start event"
assert start_event.data.reasoning_effort == "high"
diff --git a/python/e2e/test_subagent_hooks_e2e.py b/python/e2e/test_subagent_hooks_e2e.py
index e5262a23c..1ca2a54c1 100644
--- a/python/e2e/test_subagent_hooks_e2e.py
+++ b/python/e2e/test_subagent_hooks_e2e.py
@@ -7,7 +7,7 @@
import pytest
-from copilot.client import CopilotClient, SubprocessConfig
+from copilot.client import CopilotClient, RuntimeConnection
from copilot.session import PermissionHandler
from .testharness import E2ETestContext
@@ -50,12 +50,10 @@ async def on_post_tool_use(input_data, invocation):
"fake-token-for-e2e-tests" if os.environ.get("GITHUB_ACTIONS") == "true" else None
)
client = CopilotClient(
- SubprocessConfig(
- cli_path=ctx.cli_path,
- working_directory=ctx.work_dir,
- env=env,
- github_token=github_token,
- )
+ connection=RuntimeConnection.for_stdio(path=ctx.cli_path),
+ working_directory=ctx.work_dir,
+ env=env,
+ github_token=github_token,
)
session = await client.create_session(
@@ -85,7 +83,7 @@ async def on_post_tool_use(input_data, invocation):
assert len(view_pre) > 0, "preToolUse should fire for the sub-agent's 'view' tool call"
assert len(view_post) > 0, "postToolUse should fire for the sub-agent's 'view' tool call"
- # input.sessionId distinguishes parent from sub-agent
+ # input.session_id distinguishes parent from sub-agent
assert view_pre[0]["sessionId"] != task_pre[0]["sessionId"], (
"Sub-agent tool hooks should have a different sessionId than parent tool hooks"
)
diff --git a/python/e2e/test_suspend_e2e.py b/python/e2e/test_suspend_e2e.py
index ec34bfc37..b0f74140c 100644
--- a/python/e2e/test_suspend_e2e.py
+++ b/python/e2e/test_suspend_e2e.py
@@ -14,9 +14,9 @@
import pytest
-from copilot import CopilotClient
-from copilot.client import ExternalServerConfig, SubprocessConfig
-from copilot.session import PermissionHandler, PermissionRequestResult
+from copilot import CopilotClient, RuntimeConnection
+from copilot.generated.rpc import PermissionDecisionUserNotAvailable
+from copilot.session import PermissionHandler
from copilot.tools import Tool, ToolInvocation, ToolResult
from .testharness import E2ETestContext
@@ -30,15 +30,17 @@ def _make_subprocess_client(ctx: E2ETestContext, *, use_stdio: bool = True) -> C
github_token = (
"fake-token-for-e2e-tests" if os.environ.get("GITHUB_ACTIONS") == "true" else None
)
- return CopilotClient(
- SubprocessConfig(
- cli_path=ctx.cli_path,
- working_directory=ctx.work_dir,
- env=ctx.get_env(),
- github_token=github_token,
- use_stdio=use_stdio,
- tcp_connection_token="py-tcp-shared-test-token",
+ if use_stdio:
+ connection = RuntimeConnection.for_stdio(path=ctx.cli_path)
+ else:
+ connection = RuntimeConnection.for_tcp(
+ path=ctx.cli_path, connection_token="py-tcp-shared-test-token"
)
+ return CopilotClient(
+ connection=connection,
+ working_directory=ctx.work_dir,
+ env=ctx.get_env(),
+ github_token=github_token,
)
@@ -99,11 +101,13 @@ async def test_should_allow_resume_and_continue_conversation_after_suspend(
server = _make_subprocess_client(ctx, use_stdio=False)
await server.start()
try:
- cli_url = f"localhost:{server.actual_port}"
+ cli_url = f"localhost:{server.runtime_port}"
session_id: str
first_client = CopilotClient(
- ExternalServerConfig(url=cli_url, tcp_connection_token="py-tcp-shared-test-token")
+ connection=RuntimeConnection.for_uri(
+ cli_url, connection_token="py-tcp-shared-test-token"
+ )
)
try:
session1 = await first_client.create_session(
@@ -120,7 +124,9 @@ async def test_should_allow_resume_and_continue_conversation_after_suspend(
await _safe_force_stop(first_client)
resumed_client = CopilotClient(
- ExternalServerConfig(url=cli_url, tcp_connection_token="py-tcp-shared-test-token")
+ connection=RuntimeConnection.for_uri(
+ cli_url, connection_token="py-tcp-shared-test-token"
+ )
)
try:
session2 = await resumed_client.resume_session(
@@ -172,9 +178,7 @@ def tool_handler(args):
assert not tool_invoked
finally:
if not release_permission_handler.done():
- release_permission_handler.set_result(
- PermissionRequestResult(kind="user-not-available")
- )
+ release_permission_handler.set_result(PermissionDecisionUserNotAvailable())
await _safe_disconnect(session)
async def test_should_reject_pending_external_tool_when_suspending(self, ctx: E2ETestContext):
diff --git a/python/e2e/test_telemetry_e2e.py b/python/e2e/test_telemetry_e2e.py
index 6b1f7766c..f18a9fb88 100644
--- a/python/e2e/test_telemetry_e2e.py
+++ b/python/e2e/test_telemetry_e2e.py
@@ -22,9 +22,8 @@
import pytest
-from copilot import CopilotClient
+from copilot import CopilotClient, RuntimeConnection, TelemetryConfig
from copilot._telemetry import get_trace_context, trace_context
-from copilot.client import SubprocessConfig, TelemetryConfig
from copilot.session import PermissionHandler
from copilot.tools import Tool, ToolInvocation, ToolResult
@@ -82,18 +81,16 @@ def echo(invocation: ToolInvocation) -> ToolResult:
"fake-token-for-e2e-tests" if os.environ.get("GITHUB_ACTIONS") == "true" else None
)
client = CopilotClient(
- SubprocessConfig(
- cli_path=ctx.cli_path,
- working_directory=ctx.work_dir,
- env=ctx.get_env(),
- github_token=github_token,
- telemetry=TelemetryConfig(
- file_path=str(telemetry_path),
- exporter_type="file",
- source_name=source_name,
- capture_content=True,
- ),
- )
+ connection=RuntimeConnection.for_stdio(path=ctx.cli_path),
+ working_directory=ctx.work_dir,
+ env=ctx.get_env(),
+ github_token=github_token,
+ telemetry=TelemetryConfig(
+ file_path=str(telemetry_path),
+ exporter_type="file",
+ source_name=source_name,
+ capture_content=True,
+ ),
)
try:
@@ -209,18 +206,6 @@ async def test_can_set_all_properties(self):
assert cfg["capture_content"] is True
-class TestSubprocessConfigTelemetry:
- """Mirrors CopilotClientOptions_Telemetry_DefaultsToNull."""
-
- async def test_telemetry_defaults_to_none(self):
- config = SubprocessConfig()
- assert config.telemetry is None
-
- # NOTE: CopilotClientOptions_Clone_CopiesTelemetry from the C# baseline has
- # no Python equivalent: SubprocessConfig is a plain dataclass with no
- # Clone() method, so there is nothing meaningful to test.
-
-
class TestTelemetryHelpers:
"""Mirrors TelemetryHelpers_Restores_W3C_Trace_Context."""
diff --git a/python/e2e/test_tools_e2e.py b/python/e2e/test_tools_e2e.py
index 4800d97c4..2f121b46d 100644
--- a/python/e2e/test_tools_e2e.py
+++ b/python/e2e/test_tools_e2e.py
@@ -6,7 +6,8 @@
from pydantic import BaseModel, Field
from copilot import define_tool
-from copilot.session import PermissionHandler, PermissionRequestResult
+from copilot.generated.rpc import PermissionDecisionApproveOnce, PermissionDecisionReject
+from copilot.session import PermissionHandler, PermissionNoResult
from copilot.tools import Tool, ToolInvocation, ToolResult
from .testharness import E2ETestContext, get_final_assistant_message
@@ -148,7 +149,7 @@ def safe_lookup(params: LookupParams, invocation: ToolInvocation) -> str:
def tracking_handler(request, invocation):
nonlocal did_run_permission_request
did_run_permission_request = True
- return PermissionRequestResult(kind="no-result")
+ return PermissionNoResult()
session = await ctx.client.create_session(
on_permission_request=tracking_handler, tools=[safe_lookup]
@@ -191,7 +192,7 @@ def encrypt_string(params: EncryptParams, invocation: ToolInvocation) -> str:
def on_permission_request(request, invocation):
permission_requests.append(request)
- return PermissionRequestResult(kind="approve-once")
+ return PermissionDecisionApproveOnce()
session = await ctx.client.create_session(
on_permission_request=on_permission_request, tools=[encrypt_string]
@@ -202,7 +203,7 @@ def on_permission_request(request, invocation):
assert "HELLO" in assistant_message.data.content
# Should have received a custom-tool permission request
- custom_tool_requests = [r for r in permission_requests if r.kind.value == "custom-tool"]
+ custom_tool_requests = [r for r in permission_requests if r.kind == "custom-tool"]
assert len(custom_tool_requests) > 0
assert custom_tool_requests[0].tool_name == "encrypt_string"
@@ -219,7 +220,7 @@ def encrypt_string(params: EncryptParams, invocation: ToolInvocation) -> str:
return params.input.upper()
def on_permission_request(request, invocation):
- return PermissionRequestResult(kind="reject")
+ return PermissionDecisionReject()
session = await ctx.client.create_session(
on_permission_request=on_permission_request, tools=[encrypt_string]
diff --git a/python/e2e/test_ui_elicitation_multi_client_e2e.py b/python/e2e/test_ui_elicitation_multi_client_e2e.py
index 97f989ac4..398b83ee8 100644
--- a/python/e2e/test_ui_elicitation_multi_client_e2e.py
+++ b/python/e2e/test_ui_elicitation_multi_client_e2e.py
@@ -1,6 +1,6 @@
"""E2E UI Elicitation Tests (multi-client)
-Mirrors nodejs/test/e2e/ui_elicitation.test.ts — multi-client scenarios.
+Mirrors nodejs/test/e2e/ui_elicitation.test.ts — multi-client scenarios.
Tests:
- capabilities.changed fires when second client joins with elicitation handler
@@ -16,8 +16,7 @@
import pytest
import pytest_asyncio
-from copilot import CopilotClient
-from copilot.client import ExternalServerConfig, SubprocessConfig
+from copilot import CopilotClient, RuntimeConnection
from copilot.generated.session_events import CapabilitiesChangedData
from copilot.session import (
ElicitationContext,
@@ -32,7 +31,7 @@
# ---------------------------------------------------------------------------
-# Multi-client context (TCP mode) — same pattern as test_multi_client.py
+# Multi-client context (TCP mode) — same pattern as test_multi_client.py
# ---------------------------------------------------------------------------
@@ -63,14 +62,12 @@ async def setup(self):
# Client 1 uses TCP mode so additional clients can connect
self._client1 = CopilotClient(
- SubprocessConfig(
- cli_path=self.cli_path,
- working_directory=self.work_dir,
- env=self._get_env(),
- use_stdio=False,
- github_token=github_token,
- tcp_connection_token="py-tcp-shared-test-token",
- )
+ connection=RuntimeConnection.for_tcp(
+ path=self.cli_path, connection_token="py-tcp-shared-test-token"
+ ),
+ working_directory=self.work_dir,
+ env=self._get_env(),
+ github_token=github_token,
)
# Trigger connection to obtain the TCP port
@@ -79,13 +76,12 @@ async def setup(self):
)
await init_session.disconnect()
- self._actual_port = self._client1.actual_port
+ self._actual_port = self._client1.runtime_port
assert self._actual_port is not None
self._client2 = CopilotClient(
- ExternalServerConfig(
- url=f"localhost:{self._actual_port}",
- tcp_connection_token="py-tcp-shared-test-token",
+ connection=RuntimeConnection.for_uri(
+ f"localhost:{self._actual_port}", connection_token="py-tcp-shared-test-token"
)
)
@@ -139,9 +135,8 @@ def make_external_client(self) -> CopilotClient:
"""Create a new external client connected to the same CLI server."""
assert self._actual_port is not None
return CopilotClient(
- ExternalServerConfig(
- url=f"localhost:{self._actual_port}",
- tcp_connection_token="py-tcp-shared-test-token",
+ connection=RuntimeConnection.for_uri(
+ f"localhost:{self._actual_port}", connection_token="py-tcp-shared-test-token"
)
)
@@ -263,7 +258,7 @@ def on_event(event):
unsubscribe = session1.on(on_event)
- # Client 2 joins WITH elicitation handler — triggers capabilities.changed
+ # Client 2 joins WITH elicitation handler — triggers capabilities.changed
async def handler(
context: ElicitationContext,
) -> ElicitationResult:
@@ -339,7 +334,7 @@ def on_disabled(event):
unsub_disabled = session1.on(on_disabled)
- # Force-stop client 3 — destroys the socket, triggering server-side cleanup
+ # Force-stop client 3 — destroys the socket, triggering server-side cleanup
await client3.force_stop()
await asyncio.wait_for(cap_disabled.wait(), timeout=15.0)
diff --git a/python/e2e/testharness/context.py b/python/e2e/testharness/context.py
index d67311598..2ed439fbe 100644
--- a/python/e2e/testharness/context.py
+++ b/python/e2e/testharness/context.py
@@ -12,8 +12,7 @@
from pathlib import Path
from typing import Any
-from copilot import CopilotClient
-from copilot.client import SubprocessConfig
+from copilot import CopilotClient, RuntimeConnection
from .proxy import CapiProxy
@@ -80,13 +79,13 @@ async def setup(self, cli_args: list[str] | None = None):
# Create the shared client (like Node.js/Go do)
self._client = CopilotClient(
- SubprocessConfig(
- cli_path=self.cli_path,
- cli_args=cli_args or [],
- working_directory=self.work_dir,
- env=self.get_env(),
- github_token=DEFAULT_GITHUB_TOKEN,
- )
+ connection=RuntimeConnection.for_stdio(
+ path=self.cli_path,
+ args=tuple(cli_args or []),
+ ),
+ working_directory=self.work_dir,
+ env=self.get_env(),
+ github_token=DEFAULT_GITHUB_TOKEN,
)
async def teardown(self, test_failed: bool = False):
diff --git a/python/e2e/testharness/helper.py b/python/e2e/testharness/helper.py
index c603a8ec5..d64ee00b8 100644
--- a/python/e2e/testharness/helper.py
+++ b/python/e2e/testharness/helper.py
@@ -65,7 +65,7 @@ def on_event(event):
async def _get_existing_final_response(session: CopilotSession, already_idle: bool = False):
"""Check existing messages for a final response."""
- messages = await session.get_messages()
+ messages = await session.get_events()
# Find last user message
final_user_message_index = -1
diff --git a/python/test_client.py b/python/test_client.py
index 8add6975b..2ed57657e 100644
--- a/python/test_client.py
+++ b/python/test_client.py
@@ -8,25 +8,28 @@
import pytest
-from copilot import CopilotClient, define_tool
+from copilot import (
+ CopilotClient,
+ RuntimeConnection,
+ StdioRuntimeConnection,
+ define_tool,
+)
from copilot.client import (
CloudSessionOptions,
CloudSessionRepository,
- ExternalServerConfig,
ModelCapabilities,
ModelInfo,
ModelLimits,
ModelSupports,
- SubprocessConfig,
)
-from copilot.session import PermissionHandler, PermissionRequestResult
+from copilot.session import PermissionHandler, PermissionNoResult
from e2e.testharness import CLI_PATH
class TestPermissionHandlerOptional:
@pytest.mark.asyncio
async def test_create_session_allows_missing_permission_handler(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
session = await client.create_session()
@@ -36,7 +39,7 @@ async def test_create_session_allows_missing_permission_handler(self):
@pytest.mark.asyncio
async def test_create_session_allows_none_permission_handler(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
session = await client.create_session(on_permission_request=None)
@@ -46,19 +49,23 @@ async def test_create_session_allows_none_permission_handler(self):
@pytest.mark.asyncio
async def test_v2_permission_adapter_rejects_no_result(self):
- client = CopilotClient(SubprocessConfig(CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
session = await client.create_session(
- on_permission_request=lambda request, invocation: PermissionRequestResult(
- kind="no-result"
- )
+ on_permission_request=lambda request, invocation: PermissionNoResult()
)
with pytest.raises(ValueError, match="protocol v2 server"):
await client._handle_permission_request_v2(
{
"sessionId": session.session_id,
- "permissionRequest": {"kind": "write"},
+ "permissionRequest": {
+ "kind": "write",
+ "canOfferSessionApproval": True,
+ "diff": "",
+ "fileName": "test.txt",
+ "intention": "test",
+ },
}
)
finally:
@@ -66,7 +73,7 @@ async def test_v2_permission_adapter_rejects_no_result(self):
@pytest.mark.asyncio
async def test_resume_session_allows_none_permission_handler(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
session = await client.create_session(
@@ -81,7 +88,7 @@ async def test_resume_session_allows_none_permission_handler(self):
class TestCreateSessionConfig:
@pytest.mark.asyncio
async def test_create_session_forwards_cloud_options(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
captured = {}
@@ -117,47 +124,47 @@ async def mock_request(method, params):
class TestURLParsing:
def test_parse_port_only_url(self):
- client = CopilotClient(ExternalServerConfig(url="8080"))
- assert client._actual_port == 8080
+ client = CopilotClient(connection=RuntimeConnection.for_uri("8080"))
+ assert client._runtime_port == 8080
assert client._actual_host == "localhost"
assert client._is_external_server
def test_parse_host_port_url(self):
- client = CopilotClient(ExternalServerConfig(url="127.0.0.1:9000"))
- assert client._actual_port == 9000
+ client = CopilotClient(connection=RuntimeConnection.for_uri("127.0.0.1:9000"))
+ assert client._runtime_port == 9000
assert client._actual_host == "127.0.0.1"
assert client._is_external_server
def test_parse_http_url(self):
- client = CopilotClient(ExternalServerConfig(url="http://localhost:7000"))
- assert client._actual_port == 7000
+ client = CopilotClient(connection=RuntimeConnection.for_uri("http://localhost:7000"))
+ assert client._runtime_port == 7000
assert client._actual_host == "localhost"
assert client._is_external_server
def test_parse_https_url(self):
- client = CopilotClient(ExternalServerConfig(url="https://example.com:443"))
- assert client._actual_port == 443
+ client = CopilotClient(connection=RuntimeConnection.for_uri("https://example.com:443"))
+ assert client._runtime_port == 443
assert client._actual_host == "example.com"
assert client._is_external_server
def test_invalid_url_format(self):
with pytest.raises(ValueError, match="Invalid cli_url format"):
- CopilotClient(ExternalServerConfig(url="invalid-url"))
+ CopilotClient(connection=RuntimeConnection.for_uri("invalid-url"))
def test_invalid_port_too_high(self):
with pytest.raises(ValueError, match="Invalid port in cli_url"):
- CopilotClient(ExternalServerConfig(url="localhost:99999"))
+ CopilotClient(connection=RuntimeConnection.for_uri("localhost:99999"))
def test_invalid_port_zero(self):
with pytest.raises(ValueError, match="Invalid port in cli_url"):
- CopilotClient(ExternalServerConfig(url="localhost:0"))
+ CopilotClient(connection=RuntimeConnection.for_uri("localhost:0"))
def test_invalid_port_negative(self):
with pytest.raises(ValueError, match="Invalid port in cli_url"):
- CopilotClient(ExternalServerConfig(url="localhost:-1"))
+ CopilotClient(connection=RuntimeConnection.for_uri("localhost:-1"))
def test_is_external_server_true(self):
- client = CopilotClient(ExternalServerConfig(url="localhost:8080"))
+ client = CopilotClient(connection=RuntimeConnection.for_uri("localhost:8080"))
assert client._is_external_server
@@ -165,129 +172,114 @@ class TestSessionFsConfig:
def test_missing_initial_cwd(self):
with pytest.raises(ValueError, match="session_fs.initial_working_directory is required"):
CopilotClient(
- SubprocessConfig(
- cli_path=CLI_PATH,
- log_level="error",
- session_fs={
- "initial_working_directory": "",
- "session_state_path": "/session-state",
- "conventions": "posix",
- },
- )
+ connection=RuntimeConnection.for_stdio(path=CLI_PATH),
+ log_level="error",
+ session_fs={
+ "initial_working_directory": "",
+ "session_state_path": "/session-state",
+ "conventions": "posix",
+ },
)
def test_missing_session_state_path(self):
with pytest.raises(ValueError, match="session_fs.session_state_path is required"):
CopilotClient(
- SubprocessConfig(
- cli_path=CLI_PATH,
- log_level="error",
- session_fs={
- "initial_working_directory": "/",
- "session_state_path": "",
- "conventions": "posix",
- },
- )
+ connection=RuntimeConnection.for_stdio(path=CLI_PATH),
+ log_level="error",
+ session_fs={
+ "initial_working_directory": "/",
+ "session_state_path": "",
+ "conventions": "posix",
+ },
)
class TestAuthOptions:
def test_accepts_github_token(self):
client = CopilotClient(
- SubprocessConfig(
- cli_path=CLI_PATH,
- github_token="gho_test_token",
- log_level="error",
- )
+ connection=RuntimeConnection.for_stdio(path=CLI_PATH),
+ github_token="gho_test_token",
+ log_level="error",
)
- assert isinstance(client._config, SubprocessConfig)
- assert client._config.github_token == "gho_test_token"
+ assert isinstance(client._options.connection, StdioRuntimeConnection)
+ assert client._options.github_token == "gho_test_token"
def test_default_use_logged_in_user_true_without_token(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, log_level="error"))
- assert isinstance(client._config, SubprocessConfig)
- assert client._config.use_logged_in_user is True
+ client = CopilotClient(
+ connection=RuntimeConnection.for_stdio(path=CLI_PATH), log_level="error"
+ )
+ assert isinstance(client._options.connection, StdioRuntimeConnection)
+ assert client._options.use_logged_in_user is True
def test_default_use_logged_in_user_false_with_token(self):
client = CopilotClient(
- SubprocessConfig(
- cli_path=CLI_PATH,
- github_token="gho_test_token",
- log_level="error",
- )
+ connection=RuntimeConnection.for_stdio(path=CLI_PATH),
+ github_token="gho_test_token",
+ log_level="error",
)
- assert isinstance(client._config, SubprocessConfig)
- assert client._config.use_logged_in_user is False
+ assert isinstance(client._options.connection, StdioRuntimeConnection)
+ assert client._options.use_logged_in_user is False
def test_explicit_use_logged_in_user_true_with_token(self):
client = CopilotClient(
- SubprocessConfig(
- cli_path=CLI_PATH,
- github_token="gho_test_token",
- use_logged_in_user=True,
- log_level="error",
- )
+ connection=RuntimeConnection.for_stdio(path=CLI_PATH),
+ github_token="gho_test_token",
+ use_logged_in_user=True,
+ log_level="error",
)
- assert isinstance(client._config, SubprocessConfig)
- assert client._config.use_logged_in_user is True
+ assert isinstance(client._options.connection, StdioRuntimeConnection)
+ assert client._options.use_logged_in_user is True
def test_explicit_use_logged_in_user_false_without_token(self):
client = CopilotClient(
- SubprocessConfig(
- cli_path=CLI_PATH,
- use_logged_in_user=False,
- log_level="error",
- )
+ connection=RuntimeConnection.for_stdio(path=CLI_PATH),
+ use_logged_in_user=False,
+ log_level="error",
)
- assert isinstance(client._config, SubprocessConfig)
- assert client._config.use_logged_in_user is False
+ assert isinstance(client._options.connection, StdioRuntimeConnection)
+ assert client._options.use_logged_in_user is False
class TestSessionIdleTimeoutSeconds:
def test_accepts_session_idle_timeout_seconds(self):
client = CopilotClient(
- SubprocessConfig(
- cli_path=CLI_PATH,
- session_idle_timeout_seconds=600,
- log_level="error",
- )
+ connection=RuntimeConnection.for_stdio(path=CLI_PATH),
+ session_idle_timeout_seconds=600,
+ log_level="error",
)
- assert isinstance(client._config, SubprocessConfig)
- assert client._config.session_idle_timeout_seconds == 600
+ assert isinstance(client._options.connection, StdioRuntimeConnection)
+ assert client._options.session_idle_timeout_seconds == 600
def test_default_session_idle_timeout_seconds_is_none(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, log_level="error"))
- assert isinstance(client._config, SubprocessConfig)
- assert client._config.session_idle_timeout_seconds is None
+ client = CopilotClient(
+ connection=RuntimeConnection.for_stdio(path=CLI_PATH), log_level="error"
+ )
+ assert isinstance(client._options.connection, StdioRuntimeConnection)
+ assert client._options.session_idle_timeout_seconds is None
class TestCopilotHome:
def test_accepts_copilot_home(self):
client = CopilotClient(
- SubprocessConfig(
- cli_path=CLI_PATH,
- copilot_home="/custom/copilot/home",
- log_level="error",
- )
+ connection=RuntimeConnection.for_stdio(path=CLI_PATH),
+ base_directory="/custom/copilot/home",
+ log_level="error",
)
- assert isinstance(client._config, SubprocessConfig)
- assert client._config.copilot_home == "/custom/copilot/home"
+ assert isinstance(client._options.connection, StdioRuntimeConnection)
+ assert client._options.base_directory == "/custom/copilot/home"
def test_default_copilot_home_is_none(self):
client = CopilotClient(
- SubprocessConfig(
- cli_path=CLI_PATH,
- log_level="error",
- )
+ connection=RuntimeConnection.for_stdio(path=CLI_PATH), log_level="error"
)
- assert isinstance(client._config, SubprocessConfig)
- assert client._config.copilot_home is None
+ assert isinstance(client._options.connection, StdioRuntimeConnection)
+ assert client._options.base_directory is None
class TestOverridesBuiltInTool:
@pytest.mark.asyncio
async def test_overrides_built_in_tool_sent_in_tool_definition(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -316,7 +308,7 @@ def grep(params) -> str:
@pytest.mark.asyncio
async def test_resume_session_sends_overrides_built_in_tool(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -353,7 +345,7 @@ def grep(params) -> str:
class TestInstructionDirectories:
@pytest.mark.asyncio
async def test_create_session_sends_instruction_directories(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -381,7 +373,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_resume_session_sends_instruction_directories(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -430,7 +422,7 @@ def handler():
return custom_models
client = CopilotClient(
- SubprocessConfig(cli_path=CLI_PATH),
+ connection=RuntimeConnection.for_stdio(path=CLI_PATH),
on_list_models=handler,
)
await client.start()
@@ -462,7 +454,7 @@ def handler():
return custom_models
client = CopilotClient(
- SubprocessConfig(cli_path=CLI_PATH),
+ connection=RuntimeConnection.for_stdio(path=CLI_PATH),
on_list_models=handler,
)
await client.start()
@@ -491,7 +483,7 @@ async def handler():
return custom_models
client = CopilotClient(
- SubprocessConfig(cli_path=CLI_PATH),
+ connection=RuntimeConnection.for_stdio(path=CLI_PATH),
on_list_models=handler,
)
await client.start()
@@ -522,7 +514,7 @@ def handler():
return custom_models
client = CopilotClient(
- SubprocessConfig(cli_path=CLI_PATH),
+ connection=RuntimeConnection.for_stdio(path=CLI_PATH),
on_list_models=handler,
)
models = await client.list_models()
@@ -533,7 +525,7 @@ def handler():
class TestSessionConfigForwarding:
@pytest.mark.asyncio
async def test_create_session_forwards_client_name(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -554,7 +546,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_resume_session_forwards_client_name(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -584,7 +576,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_create_session_forwards_enable_session_telemetry(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -606,7 +598,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_resume_session_forwards_enable_session_telemetry(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -635,7 +627,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_create_session_forwards_provider_headers(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -656,7 +648,7 @@ async def mock_request(method, params):
"headers": {"Authorization": "Bearer provider-token"},
"model_id": "gpt-4o",
"wire_model": "my-finetune-v3",
- "max_input_tokens": 100_000,
+ "max_prompt_tokens": 100_000,
"max_output_tokens": 4096,
},
)
@@ -673,7 +665,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_resume_session_forwards_provider_headers(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -699,7 +691,7 @@ async def mock_request(method, params):
"headers": {"Authorization": "Bearer resume-token"},
"model_id": "gpt-4o",
"wire_model": "my-finetune-v3",
- "max_input_tokens": 100_000,
+ "max_prompt_tokens": 100_000,
"max_output_tokens": 4096,
},
)
@@ -716,7 +708,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_session_send_forwards_request_headers(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -748,7 +740,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_create_session_forwards_agent(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -771,7 +763,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_resume_session_forwards_agent(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -801,7 +793,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_create_session_defaults_include_sub_agent_streaming_events_to_true(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -824,7 +816,7 @@ async def mock_request(method, params):
async def test_create_session_preserves_explicit_false_include_sub_agent_streaming_events(
self,
):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -846,7 +838,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_resume_session_defaults_include_sub_agent_streaming_events_to_true(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -876,7 +868,7 @@ async def mock_request(method, params):
async def test_resume_session_preserves_explicit_false_include_sub_agent_streaming_events(
self,
):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -905,7 +897,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_resume_session_forwards_continue_pending_work(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -934,7 +926,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_resume_session_omits_continue_pending_work_by_default(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -962,7 +954,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_set_model_sends_correct_rpc(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -990,7 +982,7 @@ async def mock_request(method, params):
class TestCopilotClientContextManager:
@pytest.mark.asyncio
async def test_aenter_calls_start_and_returns_self(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
with patch.object(client, "start", new_callable=AsyncMock) as mock_start:
result = await client.__aenter__()
mock_start.assert_awaited_once()
@@ -998,7 +990,7 @@ async def test_aenter_calls_start_and_returns_self(self):
@pytest.mark.asyncio
async def test_aexit_calls_stop(self):
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
with patch.object(client, "stop", new_callable=AsyncMock) as mock_stop:
await client.__aexit__(None, None, None)
mock_stop.assert_awaited_once()
diff --git a/python/test_commands_and_elicitation.py b/python/test_commands_and_elicitation.py
index 470e2f8f3..7f708c74f 100644
--- a/python/test_commands_and_elicitation.py
+++ b/python/test_commands_and_elicitation.py
@@ -10,8 +10,7 @@
import pytest
-from copilot import CopilotClient
-from copilot.client import SubprocessConfig
+from copilot import CopilotClient, RuntimeConnection
from copilot.session import (
AutoModeSwitchRequest,
AutoModeSwitchResponse,
@@ -50,7 +49,7 @@ class TestCommands:
@pytest.mark.asyncio
async def test_forwards_commands_in_session_create_rpc(self):
"""Verifies that commands (name + description) are serialized in session.create payload."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -89,7 +88,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_forwards_commands_in_session_resume_rpc(self):
"""Verifies that commands are serialized in session.resume payload."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -127,7 +126,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_routes_command_execute_event_to_correct_handler(self):
"""Verifies the command dispatch works for command.execute events."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -198,7 +197,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_sends_error_when_command_handler_throws(self):
"""Verifies error is sent via RPC when a command handler raises."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -256,7 +255,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_sends_error_for_unknown_command(self):
"""Verifies error is sent via RPC for an unrecognized command."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -317,7 +316,7 @@ class TestUiElicitation:
@pytest.mark.asyncio
async def test_reads_capabilities_from_session_create_response(self):
"""Verifies capabilities are parsed from session.create response."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -341,7 +340,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_defaults_capabilities_when_not_injected(self):
"""Verifies capabilities default to empty when server returns none."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -358,7 +357,7 @@ async def test_defaults_capabilities_when_not_injected(self):
@pytest.mark.asyncio
async def test_elicitation_throws_when_capability_is_missing(self):
"""Verifies that UI methods throw when elicitation is not supported."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -385,7 +384,7 @@ async def test_elicitation_throws_when_capability_is_missing(self):
@pytest.mark.asyncio
async def test_confirm_throws_when_capability_is_missing(self):
"""Verifies confirm throws when elicitation is not supported."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -409,7 +408,7 @@ class TestOnElicitationContext:
@pytest.mark.asyncio
async def test_sends_request_elicitation_flag_when_handler_provided(self):
"""Verifies requestElicitation=true is sent when onElicitationContext is provided."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -441,7 +440,7 @@ async def elicitation_handler(
@pytest.mark.asyncio
async def test_does_not_send_request_elicitation_when_no_handler(self):
"""Verifies requestElicitation=false when no handler is provided."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -469,7 +468,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_sends_mode_callback_flags_when_handlers_provided(self):
"""Verifies mode callback flags are sent when handlers are provided."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -494,8 +493,8 @@ def auto_handler(
session = await client.create_session(
on_permission_request=PermissionHandler.approve_all,
- on_exit_plan_mode=exit_handler,
- on_auto_mode_switch=auto_handler,
+ on_exit_plan_mode_request=exit_handler,
+ on_auto_mode_switch_request=auto_handler,
)
assert session is not None
@@ -508,7 +507,7 @@ def auto_handler(
@pytest.mark.asyncio
async def test_sends_mode_callback_flags_on_resume_when_handlers_provided(self):
"""Verifies mode callback flags are sent on session.resume."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -528,8 +527,8 @@ async def mock_request(method, params):
await client.resume_session(
session.session_id,
on_permission_request=PermissionHandler.approve_all,
- on_exit_plan_mode=lambda request, invocation: {"approved": True},
- on_auto_mode_switch=lambda request, invocation: "yes",
+ on_exit_plan_mode_request=lambda request, invocation: {"approved": True},
+ on_auto_mode_switch_request=lambda request, invocation: "yes",
)
payload = captured["session.resume"]
@@ -541,7 +540,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_dispatches_mode_callback_requests_to_registered_handlers(self):
"""Verifies direct mode requests are dispatched to registered handlers."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -570,8 +569,8 @@ async def auto_handler(
session = await client.create_session(
on_permission_request=PermissionHandler.approve_all,
- on_exit_plan_mode=exit_handler,
- on_auto_mode_switch=auto_handler,
+ on_exit_plan_mode_request=exit_handler,
+ on_auto_mode_switch_request=auto_handler,
)
exit_result = await client._handle_exit_plan_mode_request(
@@ -603,7 +602,7 @@ async def auto_handler(
@pytest.mark.asyncio
async def test_sends_cancel_when_elicitation_handler_throws(self):
"""Verifies auto-cancel when the elicitation handler raises."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -648,7 +647,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_dispatches_elicitation_requested_event_to_handler(self):
"""Verifies that an elicitation.requested event dispatches to the handler."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -709,7 +708,7 @@ async def mock_request(method, params):
@pytest.mark.asyncio
async def test_elicitation_handler_receives_full_schema(self):
"""Verifies that requestedSchema passes type, properties, and required to handler."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
@@ -785,7 +784,7 @@ class TestCapabilitiesChanged:
@pytest.mark.asyncio
async def test_capabilities_changed_event_updates_session(self):
"""Verifies that a capabilities.changed event updates session capabilities."""
- client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH))
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
await client.start()
try:
diff --git a/python/test_event_forward_compatibility.py b/python/test_event_forward_compatibility.py
index 25b5cc2bc..8950f839b 100644
--- a/python/test_event_forward_compatibility.py
+++ b/python/test_event_forward_compatibility.py
@@ -17,10 +17,8 @@
ElicitationCompletedAction,
ElicitationRequestedMode,
ElicitationRequestedSchema,
- PermissionPromptRequest,
- PermissionPromptRequestKind,
- PermissionRequest,
- PermissionRequestKind,
+ PermissionPromptRequestMemory,
+ PermissionRequestMemory,
PermissionRequestMemoryAction,
SessionEventType,
SessionTaskCompleteData,
@@ -149,13 +147,20 @@ def test_missing_optional_fields_remain_none_after_parsing(self):
the schema default instead of ``None`` and broke ``from_dict(to_dict(x))``
round-trips for instances where the field was ``None``.
"""
+ from copilot.generated.session_events import (
+ _load_PermissionPromptRequest,
+ _load_PermissionRequest,
+ )
+
# #1141: PermissionRequest.action defaults to None when missing.
- request = PermissionRequest.from_dict({"kind": "memory", "fact": "remember this"})
+ request = _load_PermissionRequest({"kind": "memory", "fact": "remember this"})
+ assert isinstance(request, PermissionRequestMemory)
assert request.action is None
assert PermissionRequestMemoryAction.STORE.value == "store" # sanity
# #1140: PermissionPromptRequest.action defaults to None when missing.
- prompt_request = PermissionPromptRequest.from_dict({"kind": "memory"})
+ prompt_request = _load_PermissionPromptRequest({"kind": "memory", "fact": "remember this"})
+ assert isinstance(prompt_request, PermissionPromptRequestMemory)
assert prompt_request.action is None
# #1139: SessionTaskCompleteData.summary defaults to None when missing.
@@ -175,14 +180,28 @@ def test_optional_fields_round_trip_none(self):
task = SessionTaskCompleteData(success=None, summary=None)
assert SessionTaskCompleteData.from_dict(task.to_dict()) == task
- # #1140: PermissionPromptRequest round-trip with action=None.
- prompt = PermissionPromptRequest(kind=PermissionPromptRequestKind.MEMORY)
+ # #1140: PermissionPromptRequestMemory round-trip with action=None.
+ prompt = PermissionPromptRequestMemory(fact="test-fact")
assert prompt.action is None
assert "action" not in prompt.to_dict()
- assert PermissionPromptRequest.from_dict(prompt.to_dict()) == prompt
+ assert PermissionPromptRequestMemory.from_dict(prompt.to_dict()) == prompt
- # #1141: PermissionRequest round-trip with action=None.
- permission = PermissionRequest(kind=PermissionRequestKind.MEMORY)
+ # #1141: PermissionRequestMemory round-trip with action=None.
+ permission = PermissionRequestMemory(fact="test-fact")
assert permission.action is None
assert "action" not in permission.to_dict()
- assert PermissionRequest.from_dict(permission.to_dict()) == permission
+ assert PermissionRequestMemory.from_dict(permission.to_dict()) == permission
+
+ # PermissionRequest is now a discriminated union; the dispatch loader
+ # should round-trip via the correct variant class.
+ from copilot.generated.session_events import _load_PermissionRequest
+
+ round_tripped = _load_PermissionRequest(permission.to_dict())
+ assert isinstance(round_tripped, PermissionRequestMemory)
+ assert round_tripped == permission
+ # PermissionPromptRequest likewise.
+ from copilot.generated.session_events import _load_PermissionPromptRequest
+
+ round_tripped_prompt = _load_PermissionPromptRequest(prompt.to_dict())
+ assert isinstance(round_tripped_prompt, PermissionPromptRequestMemory)
+ assert round_tripped_prompt == prompt
diff --git a/python/test_rpc_generated.py b/python/test_rpc_generated.py
index 5f484add0..5d003da42 100644
--- a/python/test_rpc_generated.py
+++ b/python/test_rpc_generated.py
@@ -7,7 +7,7 @@
from copilot.generated.rpc import (
CommandsApi,
CommandsInvokeRequest,
- SlashCommandInvocationResultKind,
+ SlashCommandTextResult,
)
@@ -19,6 +19,6 @@ async def test_commands_invoke_deserializes_slash_command_result():
result = await api.invoke(CommandsInvokeRequest(name="help"))
- assert result.kind is SlashCommandInvocationResultKind.TEXT
+ assert isinstance(result, SlashCommandTextResult)
assert result.text == "hello"
assert result.markdown is True
diff --git a/python/test_telemetry.py b/python/test_telemetry.py
index d10ffeb9f..6481fd525 100644
--- a/python/test_telemetry.py
+++ b/python/test_telemetry.py
@@ -5,7 +5,7 @@
from unittest.mock import patch
from copilot._telemetry import get_trace_context, trace_context
-from copilot.client import SubprocessConfig, TelemetryConfig
+from copilot.client import TelemetryConfig
class TestGetTraceContext:
@@ -73,17 +73,6 @@ def test_telemetry_config_type(self):
assert config["otlp_endpoint"] == "http://localhost:4318"
assert config["capture_content"] is True
- def test_telemetry_config_in_subprocess_config(self):
- """TelemetryConfig can be used in SubprocessConfig."""
- config = SubprocessConfig(
- telemetry={
- "otlp_endpoint": "http://localhost:4318",
- "exporter_type": "otlp-http",
- }
- )
- assert config.telemetry is not None
- assert config.telemetry["otlp_endpoint"] == "http://localhost:4318"
-
def test_telemetry_env_var_mapping(self):
"""TelemetryConfig fields map to expected environment variable names."""
config: TelemetryConfig = {
diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts
index 52b11ed59..eae6f09b4 100644
--- a/scripts/codegen/python.ts
+++ b/scripts/codegen/python.ts
@@ -263,6 +263,412 @@ function postProcessExternalUnionAliasesForPython(code: string, aliases: Map;
+}
+function postProcessRefBasedDiscriminatedUnionsForPython(
+ code: string,
+ definitions: Record,
+ definitionCollections: DefinitionCollections
+): { code: string; unions: ResolvedRefBasedUnion[] } {
+ interface UnionInfo {
+ aliasName: string;
+ variantNames: string[];
+ discriminatorProp: string;
+ dispatch: Array<{ value: string; typeName: string }>;
+ description: string | undefined;
+ }
+ const unions: UnionInfo[] = [];
+
+ for (const [defName, definition] of Object.entries(definitions)) {
+ const variants = (definition.anyOf ?? definition.oneOf) as JSONSchema7[] | undefined;
+ if (!Array.isArray(variants) || variants.length < 2) continue;
+ if (!variants.every((v) => typeof v === "object" && v !== null && typeof v.$ref === "string")) {
+ continue;
+ }
+
+ const variantRefNames = variants.map((v) => refTypeName(v.$ref as string, definitionCollections));
+ const resolvedVariants = variants.map(
+ (v) =>
+ resolveObjectSchema(v, definitionCollections) ??
+ resolveSchema(v, definitionCollections) ??
+ v
+ );
+ if (resolvedVariants.some((rv) => !rv || rv.properties === undefined)) continue;
+
+ const discriminator = findPyDiscriminator(resolvedVariants as JSONSchema7[]);
+ if (!discriminator) continue;
+
+ const aliasName = toPascalCase(defName);
+ const dispatch = variants.map((_, i) => {
+ const discProp = (resolvedVariants[i].properties as Record)[
+ discriminator.property
+ ];
+ return {
+ value: String(discProp.const),
+ typeName: toPascalCase(variantRefNames[i]),
+ };
+ });
+
+ unions.push({
+ aliasName,
+ variantNames: variantRefNames.map(toPascalCase),
+ discriminatorProp: discriminator.property,
+ dispatch,
+ description: typeof definition.description === "string" ? definition.description : undefined,
+ });
+ }
+
+ const resolved: ResolvedRefBasedUnion[] = [];
+ if (unions.length === 0) return { code, unions: resolved };
+
+ const emittedClassNames = new Set();
+ for (const match of code.matchAll(/^class (\w+)[:\(]/gm)) {
+ emittedClassNames.add(match[1]);
+ }
+ const acronymCandidates = (name: string): string[] => {
+ const substitutions: Array<[RegExp, string]> = [
+ [/Api/g, "API"],
+ [/Mcp/g, "MCP"],
+ [/Url/g, "URL"],
+ [/Json/g, "JSON"],
+ [/Http/g, "HTTP"],
+ [/Hmac/g, "HMAC"],
+ [/Tcp/g, "TCP"],
+ [/Sql/g, "SQL"],
+ [/Id\b/g, "ID"],
+ [/Llm/g, "LLM"],
+ [/Cli/g, "CLI"],
+ ];
+ const results = new Set([name]);
+ for (const [pattern, replacement] of substitutions) {
+ for (const existing of [...results]) {
+ results.add(existing.replace(pattern, replacement));
+ }
+ }
+ return [...results];
+ };
+ const resolveActualName = (expected: string): string | undefined => {
+ for (const candidate of acronymCandidates(expected)) {
+ if (emittedClassNames.has(candidate)) return candidate;
+ }
+ return undefined;
+ };
+
+ for (const union of unions) {
+ const actualAliasName = resolveActualName(union.aliasName);
+ const actualVariantNames: string[] = [];
+ const actualDispatch: Array<{ value: string; typeName: string }> = [];
+ let allResolved = true;
+ for (let i = 0; i < union.variantNames.length; i++) {
+ const actual = resolveActualName(union.variantNames[i]);
+ if (!actual) {
+ allResolved = false;
+ break;
+ }
+ actualVariantNames.push(actual);
+ actualDispatch.push({ value: union.dispatch[i].value, typeName: actual });
+ }
+ if (!allResolved || !actualAliasName) {
+ continue;
+ }
+ resolved.push({
+ aliasName: actualAliasName,
+ discriminatorProp: union.discriminatorProp,
+ dispatch: actualDispatch,
+ });
+
+ const lines = code.split("\n");
+ let classStart = -1;
+ for (let i = 0; i < lines.length; i++) {
+ if (lines[i] === `class ${actualAliasName}:` || lines[i].startsWith(`class ${actualAliasName}(`)) {
+ classStart = i;
+ break;
+ }
+ }
+ if (classStart >= 0) {
+ let blockStart = classStart;
+ while (
+ blockStart > 0 &&
+ (lines[blockStart - 1] === "@dataclass" || /^# /.test(lines[blockStart - 1]))
+ ) {
+ blockStart--;
+ }
+ let blockEnd = classStart + 1;
+ while (blockEnd < lines.length) {
+ const ln = lines[blockEnd];
+ if (
+ /^class \w/.test(ln) ||
+ /^def \w/.test(ln) ||
+ ln === "@dataclass" ||
+ /^# (?:Experimental|Deprecated|Internal):/.test(ln)
+ ) {
+ break;
+ }
+ blockEnd++;
+ }
+ lines.splice(blockStart, blockEnd - blockStart);
+ code = lines.join("\n");
+ }
+
+ const aliasLine = union.description
+ ? `# ${union.description.replace(/\n/g, " ")}\n${actualAliasName} = ${actualVariantNames.join(" | ")}`
+ : `${actualAliasName} = ${actualVariantNames.join(" | ")}`;
+
+ const dispatcherLines: string[] = [];
+ dispatcherLines.push(`def _load_${actualAliasName}(obj: Any) -> "${actualAliasName}":`);
+ dispatcherLines.push(` assert isinstance(obj, dict)`);
+ dispatcherLines.push(` kind = obj.get(${JSON.stringify(union.discriminatorProp)})`);
+ dispatcherLines.push(` match kind:`);
+ for (const m of actualDispatch) {
+ dispatcherLines.push(` case ${JSON.stringify(m.value)}: return ${m.typeName}.from_dict(obj)`);
+ }
+ dispatcherLines.push(
+ ` case _: raise ValueError(f"Unknown ${actualAliasName} ${union.discriminatorProp}: {kind!r}")`
+ );
+
+ code = `${code.trimEnd()}\n\n\n${aliasLine}\n\n\n${dispatcherLines.join("\n")}\n`;
+ }
+
+ code = applyUnionRewritesToPython(code, resolved);
+ return { code, unions: resolved };
+}
+
+/**
+ * Rewrite occurrences of `Name.from_dict(...)` to `_load_Name(...)` and
+ * `to_class(Name, x)` to `(x).to_dict()` for each union the caller passes in.
+ * Safe to apply repeatedly — re-running on already-rewritten code is a no-op.
+ */
+function applyUnionRewritesToPython(code: string, unions: ResolvedRefBasedUnion[]): string {
+ for (const union of unions) {
+ code = code.replace(
+ new RegExp(`\\b${union.aliasName}\\.from_dict\\b`, "g"),
+ `_load_${union.aliasName}`
+ );
+ code = code.replace(
+ new RegExp(`to_class\\(${union.aliasName},\\s*([^,)]+)\\)`, "g"),
+ `($1).to_dict()`
+ );
+ }
+ return code;
+}
+
+/**
+ * For each discriminated-union variant class, replace the dataclass-level
+ * discriminator field (e.g. ``kind: PermissionDecisionApproveOnceKind``) with
+ * a class-level constant (e.g. ``kind: ClassVar[str] = "approve-once"``).
+ * This lets users construct variants without supplying the discriminator
+ * value (``PermissionDecisionApproveOnce()`` instead of
+ * ``PermissionDecisionApproveOnce(kind=PermissionDecisionApproveOnceKind.APPROVE_ONCE)``),
+ * matching the TS / Rust / .NET / Go ergonomics for the same schema.
+ *
+ * Also rewrites the generated ``from_dict`` to skip parsing the discriminator
+ * (the dispatcher routed based on it; the variant class identity carries it)
+ * and ``to_dict`` to emit the constant directly.
+ */
+function postProcessDiscriminatorDefaultsForPython(
+ code: string,
+ unions: ResolvedRefBasedUnion[]
+): string {
+ // Build variant lookup: variant class name → { prop, value }.
+ const variantInfo = new Map();
+ for (const union of unions) {
+ for (const d of union.dispatch) {
+ // First-wins; multiple unions referencing the same variant share a
+ // discriminator/value pair anyway.
+ if (!variantInfo.has(d.typeName)) {
+ variantInfo.set(d.typeName, { prop: union.discriminatorProp, value: d.value });
+ }
+ }
+ }
+ if (variantInfo.size === 0) return code;
+
+ const lines = code.split("\n");
+ const out: string[] = [];
+ let usedClassVar = false;
+
+ let i = 0;
+ while (i < lines.length) {
+ const line = lines[i];
+ const classMatch = line.match(/^class (\w+)[:\(]/);
+ if (!classMatch) {
+ out.push(line);
+ i++;
+ continue;
+ }
+ const className = classMatch[1];
+ const info = variantInfo.get(className);
+ if (!info) {
+ out.push(line);
+ i++;
+ continue;
+ }
+
+ // Find the bounds of this class block: everything indented under it.
+ const classStart = i;
+ let classEnd = i + 1;
+ while (classEnd < lines.length) {
+ const ln = lines[classEnd];
+ if (
+ /^class \w/.test(ln) ||
+ /^def \w/.test(ln) ||
+ ln === "@dataclass" ||
+ /^# (?:Experimental|Deprecated|Internal):/.test(ln) ||
+ ln.startsWith("@dataclass(")
+ ) {
+ break;
+ }
+ classEnd++;
+ }
+ const block = lines.slice(classStart, classEnd);
+
+ // Locate the discriminator field declaration. Quicktype emits
+ // ` kind: PermissionDecisionApproveOnceKind` while the
+ // session-events codegen emits ` kind: str` — both match the
+ // simple `: ` shape (no default value, since the
+ // field is required in the schema).
+ const fieldPattern = new RegExp(`^(\\s+)${info.prop}: [\\w\\[\\], ]+$`);
+ let fieldIdx = -1;
+ for (let j = 1; j < block.length; j++) {
+ if (fieldPattern.test(block[j])) {
+ fieldIdx = j;
+ break;
+ }
+ }
+ if (fieldIdx < 0) {
+ // Variant class without an explicit discriminator field — leave alone.
+ out.push(...block);
+ i = classEnd;
+ continue;
+ }
+ const fieldIndent = (block[fieldIdx].match(/^(\s+)/) ?? ["", ""])[1];
+ const literal = JSON.stringify(info.value);
+ // Replace the field with a class-level constant.
+ block[fieldIdx] = `${fieldIndent}${info.prop}: ClassVar[str] = ${literal}`;
+ usedClassVar = true;
+
+ // Drop any field-trailing docstring lines that immediately followed the
+ // original field. Quicktype emits """..."""-style block strings; the
+ // session-events codegen does not emit per-field docstrings. We only
+ // touch the line at fieldIdx+1 if it's a docstring or blank.
+ // (Conservative: leave additional lines in place; they don't reference
+ // the dropped enum.)
+
+ // Rewrite from_dict / to_dict bodies.
+ for (let j = fieldIdx + 1; j < block.length; j++) {
+ const ln = block[j];
+
+ // Drop ` = ...(obj.get(""))` parse line in from_dict.
+ const propAssignPattern = new RegExp(
+ `^\\s+${info.prop} = .+\\(obj\\.get\\(${JSON.stringify(info.prop)}\\)\\)`
+ );
+ if (propAssignPattern.test(ln)) {
+ block[j] = "<<>>";
+ continue;
+ }
+
+ // Drop multi-line constructor kwarg of the form ` kind=kind,` —
+ // emitted by the session-events codegen when the constructor call
+ // is broken across lines.
+ const multilineKwargPattern = new RegExp(
+ `^\\s+${info.prop}=${info.prop},?\\s*$`
+ );
+ if (multilineKwargPattern.test(ln)) {
+ block[j] = "<<>>";
+ continue;
+ }
+
+ // Convert `return X(a, prop, b)` (single-line positional) to drop
+ // the prop arg. Quicktype-emitted constructors are single-line.
+ const ctorMatch = ln.match(new RegExp(`^(\\s+)return ${className}\\((.*)\\)\\s*$`));
+ if (ctorMatch) {
+ const argList = ctorMatch[2];
+ const args = splitTopLevelCommasMulti(argList);
+ const filtered = args
+ .map((a) => a.trim())
+ .filter((a) => {
+ const kw = a.match(/^([a-zA-Z_]\w*)\s*=/);
+ const name = kw ? kw[1] : a;
+ return name !== info.prop;
+ });
+ block[j] = `${ctorMatch[1]}return ${className}(${filtered.join(", ")})`;
+ continue;
+ }
+
+ // Rewrite `result[""] = to_enum(, self.)` to
+ // emit the class-level constant directly.
+ const toDictPattern = new RegExp(
+ `^(\\s+)result\\[${JSON.stringify(info.prop)}\\] = .+`
+ );
+ if (toDictPattern.test(ln)) {
+ const indent = (ln.match(/^(\s+)/) ?? ["", ""])[1];
+ block[j] = `${indent}result[${JSON.stringify(info.prop)}] = self.${info.prop}`;
+ continue;
+ }
+ }
+
+ out.push(...block.filter((l) => l !== "<<>>"));
+ i = classEnd;
+ }
+
+ let result = out.join("\n");
+ if (usedClassVar) {
+ result = ensureClassVarImport(result);
+ }
+ return result;
+}
+
+function splitTopLevelCommasMulti(s: string): string[] {
+ const parts: string[] = [];
+ let depth = 0;
+ let start = 0;
+ for (let i = 0; i < s.length; i++) {
+ const c = s[i];
+ if (c === "(" || c === "[" || c === "{") depth++;
+ else if (c === ")" || c === "]" || c === "}") depth--;
+ else if (c === "," && depth === 0) {
+ parts.push(s.slice(start, i));
+ start = i + 1;
+ }
+ }
+ parts.push(s.slice(start));
+ return parts.filter((p) => p.trim().length > 0);
+}
+
+function ensureClassVarImport(code: string): string {
+ // Already imported?
+ if (/\bfrom typing import [^\n]*\bClassVar\b/.test(code)) return code;
+ return code.replace(
+ /^from typing import (.+)$/m,
+ (_match, names) => {
+ const list = names.split(",").map((n: string) => n.trim()).filter(Boolean);
+ list.push("ClassVar");
+ list.sort();
+ return `from typing import ${[...new Set(list)].join(", ")}`;
+ }
+ );
+}
+
function pushPyExperimentalComment(lines: string[], subject: PyExperimentalSubject, indent = ""): void {
lines.push(pyExperimentalComment(subject, indent));
}
@@ -859,6 +1265,7 @@ interface PyCodegenCtx {
usesTimedelta: boolean;
usesIntegerTimedelta: boolean;
definitions: DefinitionCollections;
+ refBasedUnions: ResolvedRefBasedUnion[];
}
function toEnumMemberName(value: string): string {
@@ -969,6 +1376,104 @@ function pyDurationResolvedType(ctx: PyCodegenCtx, isInteger: boolean): PyResolv
};
}
+/**
+ * Emit a "$ref-based discriminated union" — a Python equivalent of the
+ * polymorphic hierarchies that TS / Rust / .NET / Go produce for the same
+ * schema shape. Given a definition like
+ *
+ * "PermissionRequest": { "anyOf": [ {"$ref": "#/.../PermissionRequestShell"}, ... ] }
+ *
+ * where every variant is a `$ref` to a sibling definition and the variants
+ * share a `const` discriminator property (e.g. `kind`), emit each variant as a
+ * standalone `@dataclass`, plus a union alias and a `from_dict` dispatcher.
+ *
+ * Returns the resolved type or `undefined` if the schema doesn't match the
+ * expected shape (caller falls back to other paths).
+ */
+function tryEmitPyRefBasedDiscriminatedUnion(
+ aliasName: string,
+ resolved: JSONSchema7,
+ ctx: PyCodegenCtx
+): PyResolvedType | undefined {
+ const variants = (resolved.anyOf ?? resolved.oneOf) as JSONSchema7[] | undefined;
+ if (!Array.isArray(variants) || variants.length < 2) return undefined;
+
+ const variantRefNames: string[] = [];
+ for (const v of variants) {
+ if (!v || typeof v !== "object") return undefined;
+ const ref = (v as JSONSchema7).$ref;
+ if (typeof ref !== "string" || !ref.startsWith("#/definitions/")) {
+ return undefined;
+ }
+ variantRefNames.push(refTypeName(ref, ctx.definitions));
+ }
+
+ const resolvedVariants = variants.map(
+ (v) =>
+ resolveObjectSchema(v, ctx.definitions) ??
+ resolveSchema(v, ctx.definitions) ??
+ (v as JSONSchema7)
+ );
+ if (resolvedVariants.some((rv) => !rv || rv.properties === undefined)) {
+ return undefined;
+ }
+ const discriminator = findPyDiscriminator(resolvedVariants as JSONSchema7[]);
+ if (!discriminator) return undefined;
+
+ const variantTypeNames: string[] = [];
+ const dispatch: Array<{ value: string; typeName: string }> = [];
+ for (let i = 0; i < variants.length; i++) {
+ const variantTypeName = toPascalCase(variantRefNames[i]);
+ const variantSchema = resolveObjectSchema(variants[i], ctx.definitions);
+ if (variantSchema) {
+ emitPyClass(variantTypeName, variantSchema, ctx, variantSchema.description);
+ }
+ variantTypeNames.push(variantTypeName);
+ const discProp = resolvedVariants[i].properties?.[discriminator.property] as JSONSchema7;
+ dispatch.push({ value: String(discProp.const), typeName: variantTypeName });
+ }
+
+ if (!ctx.aliasesByName.has(aliasName)) {
+ const lines: string[] = [];
+ if (resolved.description) {
+ lines.push(`# ${resolved.description}`);
+ }
+ lines.push(`${aliasName} = ${variantTypeNames.join(" | ")}`);
+ ctx.aliasesByName.add(aliasName);
+ ctx.aliases.push(lines.join("\n"));
+ ctx.refBasedUnions.push({
+ aliasName,
+ discriminatorProp: discriminator.property,
+ dispatch,
+ });
+ }
+
+ const dispatcherName = `_load_${aliasName}`;
+ if (!ctx.generatedNames.has(dispatcherName)) {
+ ctx.generatedNames.add(dispatcherName);
+ const lines: string[] = [];
+ lines.push(`def ${dispatcherName}(obj: Any) -> "${aliasName}":`);
+ lines.push(` assert isinstance(obj, dict)`);
+ lines.push(` kind = obj.get(${JSON.stringify(discriminator.property)})`);
+ lines.push(` match kind:`);
+ for (const m of dispatch) {
+ lines.push(
+ ` case ${JSON.stringify(m.value)}: return ${m.typeName}.from_dict(obj)`
+ );
+ }
+ lines.push(
+ ` case _: raise ValueError(f"Unknown ${aliasName} ${discriminator.property}: {kind!r}")`
+ );
+ ctx.classes.push(lines.join("\n"));
+ }
+
+ return {
+ annotation: aliasName,
+ fromExpr: (expr) => `${dispatcherName}(${expr})`,
+ toExpr: (expr) => `${expr}.to_dict()`,
+ };
+}
+
function isPyBase64StringSchema(schema: JSONSchema7): boolean {
return schema.format === "byte" || (schema as Record).contentEncoding === "base64";
}
@@ -1228,6 +1733,17 @@ function resolvePyPropertyType(
return isRequired ? enumResolved : pyOptionalResolvedType(enumResolved);
}
+ // Emit "$ref"-based discriminated unions as proper Python unions
+ // (per-variant dataclasses + alias + dispatcher) rather than flat
+ // merged dataclasses. Matches the polymorphic hierarchies emitted
+ // by the TS / Rust / .NET / Go SDKs for the same schema shape.
+ if (resolved.anyOf || resolved.oneOf) {
+ const unionResolved = tryEmitPyRefBasedDiscriminatedUnion(typeName, resolved, ctx);
+ if (unionResolved) {
+ return isRequired ? unionResolved : pyOptionalResolvedType(unionResolved);
+ }
+ }
+
const resolvedObject = resolveObjectSchema(propSchema, ctx.definitions);
if (isNamedPyObjectSchema(resolvedObject)) {
emitPyClass(typeName, resolvedObject, ctx, resolvedObject.description);
@@ -1275,6 +1791,21 @@ function resolvePyPropertyType(
if (nonNull.length > 1) {
const discriminator = findPyDiscriminator(nonNull);
if (discriminator) {
+ // Prefer the proper per-variant union shape when every variant
+ // is a `$ref` to a sibling definition. Same rationale as in the
+ // top-level $ref branch above: matches TS/Rust/.NET/Go.
+ if (variantSchemas.every((s) => typeof s.$ref === "string")) {
+ const unionResolved = tryEmitPyRefBasedDiscriminatedUnion(
+ nestedName,
+ propSchema,
+ ctx
+ );
+ if (unionResolved) {
+ return hasNull || !isRequired
+ ? pyOptionalResolvedType(unionResolved)
+ : unionResolved;
+ }
+ }
emitPyFlatDiscriminatedUnion(
nestedName,
discriminator.property,
@@ -1753,6 +2284,7 @@ export function generatePythonSessionEventsCode(schema: JSONSchema7): string {
usesTimedelta: false,
usesIntegerTimedelta: false,
definitions: collectDefinitionCollections(schema as Record),
+ refBasedUnions: [],
};
for (const variant of variants) {
@@ -2082,7 +2614,9 @@ export function generatePythonSessionEventsCode(schema: JSONSchema7): string {
out.push(``);
out.push(``);
- return postProcessPythonSessionEventCode(out.join("\n"));
+ let finalCode = postProcessPythonSessionEventCode(out.join("\n"));
+ finalCode = postProcessDiscriminatorDefaultsForPython(finalCode, ctx.refBasedUnions);
+ return finalCode;
}
async function generateSessionEvents(schemaPath?: string): Promise {
@@ -2206,6 +2740,14 @@ async function generateRpc(schemaPath?: string, sessionEventsSchema?: JSONSchema
inputData,
lang: "python",
rendererOptions: { "python-version": "3.7" },
+ // Disable quicktype's structural-equality merging of class types.
+ // It produces fuzzy synthesized names (e.g. ``PermissionDecisionApproveForIonApproval``
+ // as the merge of ``PermissionDecisionApproveFor{Session,Location}Approval``) which
+ // are unstable: any future divergence between the variants would silently change
+ // the generated class name. We rely on the schema's named definitions and resolve
+ // structural unions via :func:`postProcessRefBasedDiscriminatedUnionsForPython`,
+ // so the merging is also redundant.
+ inferenceFlags: { combineClasses: false },
});
let typesCode = qtResult.lines.join("\n");
@@ -2221,6 +2763,12 @@ async function generateRpc(schemaPath?: string, sessionEventsSchema?: JSONSchema
typesCode = collapsePlaceholderPythonDataclasses(typesCode, knownDefNames);
typesCode = postProcessExternalUnionAliasesForPython(typesCode, externalUnionAliases);
typesCode = postProcessExternalRefsForPython(typesCode, externalRefs.placeholderNames, externalEnumNames);
+ const { code: typesCodeAfterUnions, unions: refBasedUnions } = postProcessRefBasedDiscriminatedUnionsForPython(
+ typesCode,
+ allDefinitions,
+ allDefinitionCollections
+ );
+ typesCode = typesCodeAfterUnions;
typesCode = modernizePython(typesCode);
// Fix quicktype's Enum-suffix renaming: quicktype sometimes renames "Xyz" to
@@ -2370,6 +2918,7 @@ async function generateRpc(schemaPath?: string, sessionEventsSchema?: JSONSchema
AUTO-GENERATED FILE - DO NOT EDIT
Generated from: api.schema.json
"""
+from __future__ import annotations
from typing import TYPE_CHECKING
@@ -2461,6 +3010,11 @@ def _patch_model_capabilities(data: dict) -> dict:
/(_patch_model_capabilities\(await self\._client\.request\("models\.list"[^)]*\)[^)]*\))/,
"$1)",
);
+ // Apply union rewrites to the assembled code so RPC method wrappers
+ // generated after the types section also route Name.from_dict / to_class
+ // through the discriminator dispatcher.
+ finalCode = applyUnionRewritesToPython(finalCode, refBasedUnions);
+ finalCode = postProcessDiscriminatorDefaultsForPython(finalCode, refBasedUnions);
finalCode = unwrapRedundantPythonLambdas(finalCode);
const outPath = await writeGeneratedFile("python/copilot/generated/rpc.py", finalCode);
diff --git a/test/scenarios/auth/byok-anthropic/python/main.py b/test/scenarios/auth/byok-anthropic/python/main.py
index 3ad893ba5..5b623ff87 100644
--- a/test/scenarios/auth/byok-anthropic/python/main.py
+++ b/test/scenarios/auth/byok-anthropic/python/main.py
@@ -1,8 +1,8 @@
import asyncio
import os
import sys
+
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY")
ANTHROPIC_MODEL = os.environ.get("ANTHROPIC_MODEL", "claude-sonnet-4-20250514")
@@ -14,29 +14,25 @@
async def main():
- client = CopilotClient(SubprocessConfig(
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ client = CopilotClient()
try:
- session = await client.create_session({
- "model": ANTHROPIC_MODEL,
- "provider": {
+ session = await client.create_session(
+ model=ANTHROPIC_MODEL,
+ provider={
"type": "anthropic",
"base_url": ANTHROPIC_BASE_URL,
"api_key": ANTHROPIC_API_KEY,
},
- "available_tools": [],
- "system_message": {
+ available_tools=[],
+ system_message={
"mode": "replace",
"content": "You are a helpful assistant. Answer concisely.",
},
- })
-
- response = await session.send_and_wait(
- "What is the capital of France?"
)
+ response = await session.send_and_wait("What is the capital of France?")
+
if response:
print(response.data.content)
diff --git a/test/scenarios/auth/byok-azure/python/main.py b/test/scenarios/auth/byok-azure/python/main.py
index 1ae214261..031ff47ee 100644
--- a/test/scenarios/auth/byok-azure/python/main.py
+++ b/test/scenarios/auth/byok-azure/python/main.py
@@ -1,8 +1,8 @@
import asyncio
import os
import sys
+
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
AZURE_OPENAI_ENDPOINT = os.environ.get("AZURE_OPENAI_ENDPOINT")
AZURE_OPENAI_API_KEY = os.environ.get("AZURE_OPENAI_API_KEY")
@@ -15,14 +15,12 @@
async def main():
- client = CopilotClient(SubprocessConfig(
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ client = CopilotClient()
try:
- session = await client.create_session({
- "model": AZURE_OPENAI_MODEL,
- "provider": {
+ session = await client.create_session(
+ model=AZURE_OPENAI_MODEL,
+ provider={
"type": "azure",
"base_url": AZURE_OPENAI_ENDPOINT,
"api_key": AZURE_OPENAI_API_KEY,
@@ -30,17 +28,15 @@ async def main():
"api_version": AZURE_API_VERSION,
},
},
- "available_tools": [],
- "system_message": {
+ available_tools=[],
+ system_message={
"mode": "replace",
"content": "You are a helpful assistant. Answer concisely.",
},
- })
-
- response = await session.send_and_wait(
- "What is the capital of France?"
)
+ response = await session.send_and_wait("What is the capital of France?")
+
if response:
print(response.data.content)
diff --git a/test/scenarios/auth/byok-ollama/python/main.py b/test/scenarios/auth/byok-ollama/python/main.py
index 78019acd7..90c4838f8 100644
--- a/test/scenarios/auth/byok-ollama/python/main.py
+++ b/test/scenarios/auth/byok-ollama/python/main.py
@@ -1,8 +1,7 @@
import asyncio
import os
-import sys
+
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "http://localhost:11434/v1")
OLLAMA_MODEL = os.environ.get("OLLAMA_MODEL", "llama3.2:3b")
@@ -13,28 +12,24 @@
async def main():
- client = CopilotClient(SubprocessConfig(
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ client = CopilotClient()
try:
- session = await client.create_session({
- "model": OLLAMA_MODEL,
- "provider": {
+ session = await client.create_session(
+ model=OLLAMA_MODEL,
+ provider={
"type": "openai",
"base_url": OLLAMA_BASE_URL,
},
- "available_tools": [],
- "system_message": {
+ available_tools=[],
+ system_message={
"mode": "replace",
"content": COMPACT_SYSTEM_PROMPT,
},
- })
-
- response = await session.send_and_wait(
- "What is the capital of France?"
)
+ response = await session.send_and_wait("What is the capital of France?")
+
if response:
print(response.data.content)
diff --git a/test/scenarios/auth/byok-openai/python/main.py b/test/scenarios/auth/byok-openai/python/main.py
index 8362963b2..e9c673aa0 100644
--- a/test/scenarios/auth/byok-openai/python/main.py
+++ b/test/scenarios/auth/byok-openai/python/main.py
@@ -1,8 +1,8 @@
import asyncio
import os
import sys
+
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
OPENAI_BASE_URL = os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1")
OPENAI_MODEL = os.environ.get("OPENAI_MODEL", "claude-haiku-4.5")
@@ -14,24 +14,20 @@
async def main():
- client = CopilotClient(SubprocessConfig(
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ client = CopilotClient()
try:
- session = await client.create_session({
- "model": OPENAI_MODEL,
- "provider": {
+ session = await client.create_session(
+ model=OPENAI_MODEL,
+ provider={
"type": "openai",
"base_url": OPENAI_BASE_URL,
"api_key": OPENAI_API_KEY,
},
- })
-
- response = await session.send_and_wait(
- "What is the capital of France?"
)
+ response = await session.send_and_wait("What is the capital of France?")
+
if response:
print(response.data.content)
diff --git a/test/scenarios/auth/gh-app/python/main.py b/test/scenarios/auth/gh-app/python/main.py
index afba29254..0d5a5ee9d 100644
--- a/test/scenarios/auth/gh-app/python/main.py
+++ b/test/scenarios/auth/gh-app/python/main.py
@@ -5,8 +5,6 @@
import urllib.request
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
-
DEVICE_CODE_URL = "https://github.com/login/device/code"
ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token"
@@ -61,7 +59,9 @@ def poll_for_access_token(client_id: str, device_code: str, interval: int) -> st
if data.get("error") == "slow_down":
delay_seconds = int(data.get("interval", delay_seconds + 5))
continue
- raise RuntimeError(data.get("error_description") or data.get("error") or "OAuth polling failed")
+ raise RuntimeError(
+ data.get("error_description") or data.get("error") or "OAuth polling failed"
+ )
async def main():
@@ -79,13 +79,10 @@ async def main():
display_name = f" ({user.get('name')})" if user.get("name") else ""
print(f"Authenticated as: {user.get('login')}{display_name}")
- client = CopilotClient(SubprocessConfig(
- github_token=token,
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ client = CopilotClient(github_token=token)
try:
- session = await client.create_session({"model": "claude-haiku-4.5"})
+ session = await client.create_session(model="claude-haiku-4.5")
response = await session.send_and_wait("What is the capital of France?")
if response:
print(response.data.content)
diff --git a/test/scenarios/bundling/app-backend-to-server/python/main.py b/test/scenarios/bundling/app-backend-to-server/python/main.py
index 2684a30b8..d53d89854 100644
--- a/test/scenarios/bundling/app-backend-to-server/python/main.py
+++ b/test/scenarios/bundling/app-backend-to-server/python/main.py
@@ -4,9 +4,9 @@
import sys
import urllib.request
-from flask import Flask, request, jsonify
-from copilot import CopilotClient
-from copilot.client import ExternalServerConfig
+from flask import Flask, jsonify, request
+
+from copilot import CopilotClient, RuntimeConnection
app = Flask(__name__)
@@ -14,10 +14,10 @@
async def ask_copilot(prompt: str) -> str:
- client = CopilotClient(ExternalServerConfig(url=CLI_URL))
+ client = CopilotClient(connection=RuntimeConnection.for_uri(CLI_URL))
try:
- session = await client.create_session({"model": "claude-haiku-4.5"})
+ session = await client.create_session(model="claude-haiku-4.5")
response = await session.send_and_wait(prompt)
@@ -70,6 +70,7 @@ def self_test(port: int):
)
server_thread.start()
import time
+
time.sleep(1)
self_test(port)
else:
diff --git a/test/scenarios/bundling/app-direct-server/python/main.py b/test/scenarios/bundling/app-direct-server/python/main.py
index b441bec51..1bf32b475 100644
--- a/test/scenarios/bundling/app-direct-server/python/main.py
+++ b/test/scenarios/bundling/app-direct-server/python/main.py
@@ -1,20 +1,20 @@
import asyncio
import os
-from copilot import CopilotClient
-from copilot.client import ExternalServerConfig
+
+from copilot import CopilotClient, RuntimeConnection
async def main():
- client = CopilotClient(ExternalServerConfig(
- url=os.environ.get("COPILOT_CLI_URL", "localhost:3000"),
- ))
+ client = CopilotClient(
+ connection=RuntimeConnection.for_uri(
+ os.environ.get("COPILOT_CLI_URL", "localhost:3000"),
+ ),
+ )
try:
- session = await client.create_session({"model": "claude-haiku-4.5"})
+ session = await client.create_session(model="claude-haiku-4.5")
- response = await session.send_and_wait(
- "What is the capital of France?"
- )
+ response = await session.send_and_wait("What is the capital of France?")
if response:
print(response.data.content)
diff --git a/test/scenarios/bundling/container-proxy/proxy.py b/test/scenarios/bundling/container-proxy/proxy.py
index afe999a4c..688b9f8c1 100644
--- a/test/scenarios/bundling/container-proxy/proxy.py
+++ b/test/scenarios/bundling/container-proxy/proxy.py
@@ -12,7 +12,7 @@
import json
import sys
import time
-from http.server import HTTPServer, BaseHTTPRequestHandler
+from http.server import BaseHTTPRequestHandler, HTTPServer
class ProxyHandler(BaseHTTPRequestHandler):
diff --git a/test/scenarios/bundling/container-proxy/python/main.py b/test/scenarios/bundling/container-proxy/python/main.py
index b441bec51..1bf32b475 100644
--- a/test/scenarios/bundling/container-proxy/python/main.py
+++ b/test/scenarios/bundling/container-proxy/python/main.py
@@ -1,20 +1,20 @@
import asyncio
import os
-from copilot import CopilotClient
-from copilot.client import ExternalServerConfig
+
+from copilot import CopilotClient, RuntimeConnection
async def main():
- client = CopilotClient(ExternalServerConfig(
- url=os.environ.get("COPILOT_CLI_URL", "localhost:3000"),
- ))
+ client = CopilotClient(
+ connection=RuntimeConnection.for_uri(
+ os.environ.get("COPILOT_CLI_URL", "localhost:3000"),
+ ),
+ )
try:
- session = await client.create_session({"model": "claude-haiku-4.5"})
+ session = await client.create_session(model="claude-haiku-4.5")
- response = await session.send_and_wait(
- "What is the capital of France?"
- )
+ response = await session.send_and_wait("What is the capital of France?")
if response:
print(response.data.content)
diff --git a/test/scenarios/bundling/fully-bundled/python/main.py b/test/scenarios/bundling/fully-bundled/python/main.py
index 39ce2bb81..63c309995 100644
--- a/test/scenarios/bundling/fully-bundled/python/main.py
+++ b/test/scenarios/bundling/fully-bundled/python/main.py
@@ -1,21 +1,18 @@
import asyncio
import os
+
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
- session = await client.create_session({"model": "claude-haiku-4.5"})
+ session = await client.create_session(model="claude-haiku-4.5")
- response = await session.send_and_wait(
- "What is the capital of France?"
- )
+ response = await session.send_and_wait("What is the capital of France?")
if response:
print(response.data.content)
diff --git a/test/scenarios/callbacks/hooks/python/main.py b/test/scenarios/callbacks/hooks/python/main.py
index ba224ef24..3a7b5906c 100644
--- a/test/scenarios/callbacks/hooks/python/main.py
+++ b/test/scenarios/callbacks/hooks/python/main.py
@@ -1,15 +1,14 @@
import asyncio
import os
-from copilot import CopilotClient
-from copilot.client import SubprocessConfig
-from copilot.session import PermissionRequestResult
+from copilot import CopilotClient
+from copilot.generated.rpc import PermissionDecisionApproveOnce
hook_log: list[str] = []
async def auto_approve_permission(request, invocation):
- return PermissionRequestResult(kind="approve-once")
+ return PermissionDecisionApproveOnce()
async def on_session_start(input_data, invocation):
@@ -42,25 +41,22 @@ async def on_error_occurred(input_data, invocation):
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
session = await client.create_session(
- {
- "model": "claude-haiku-4.5",
- "on_permission_request": auto_approve_permission,
- "hooks": {
- "on_session_start": on_session_start,
- "on_session_end": on_session_end,
- "on_pre_tool_use": on_pre_tool_use,
- "on_post_tool_use": on_post_tool_use,
- "on_user_prompt_submitted": on_user_prompt_submitted,
- "on_error_occurred": on_error_occurred,
- },
- }
+ model="claude-haiku-4.5",
+ on_permission_request=auto_approve_permission,
+ hooks={
+ "on_session_start": on_session_start,
+ "on_session_end": on_session_end,
+ "on_pre_tool_use": on_pre_tool_use,
+ "on_post_tool_use": on_post_tool_use,
+ "on_user_prompt_submitted": on_user_prompt_submitted,
+ "on_error_occurred": on_error_occurred,
+ },
)
response = await session.send_and_wait(
diff --git a/test/scenarios/callbacks/permissions/python/main.py b/test/scenarios/callbacks/permissions/python/main.py
index 677ca58d0..138d6310a 100644
--- a/test/scenarios/callbacks/permissions/python/main.py
+++ b/test/scenarios/callbacks/permissions/python/main.py
@@ -1,8 +1,8 @@
import asyncio
import os
+
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
-from copilot.session import PermissionRequestResult
+from copilot.generated.rpc import PermissionDecisionApproveOnce
# Track which tools requested permission
permission_log: list[str] = []
@@ -10,7 +10,7 @@
async def log_permission(request, invocation):
permission_log.append(f"approved:{request.tool_name}")
- return PermissionRequestResult(kind="approve-once")
+ return PermissionDecisionApproveOnce()
async def auto_approve_tool(input_data, invocation):
@@ -18,18 +18,15 @@ async def auto_approve_tool(input_data, invocation):
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
session = await client.create_session(
- {
- "model": "claude-haiku-4.5",
- "on_permission_request": log_permission,
- "hooks": {"on_pre_tool_use": auto_approve_tool},
- }
+ model="claude-haiku-4.5",
+ on_permission_request=log_permission,
+ hooks={"on_pre_tool_use": auto_approve_tool},
)
response = await session.send_and_wait(
diff --git a/test/scenarios/callbacks/user-input/python/main.py b/test/scenarios/callbacks/user-input/python/main.py
index 07a7eb40e..9eff3c7cc 100644
--- a/test/scenarios/callbacks/user-input/python/main.py
+++ b/test/scenarios/callbacks/user-input/python/main.py
@@ -1,15 +1,14 @@
import asyncio
import os
-from copilot import CopilotClient
-from copilot.client import SubprocessConfig
-from copilot.session import PermissionRequestResult
+from copilot import CopilotClient
+from copilot.generated.rpc import PermissionDecisionApproveOnce
input_log: list[str] = []
async def auto_approve_permission(request, invocation):
- return PermissionRequestResult(kind="approve-once")
+ return PermissionDecisionApproveOnce()
async def auto_approve_tool(input_data, invocation):
@@ -22,19 +21,16 @@ async def handle_user_input(request, invocation):
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
session = await client.create_session(
- {
- "model": "claude-haiku-4.5",
- "on_permission_request": auto_approve_permission,
- "on_user_input_request": handle_user_input,
- "hooks": {"on_pre_tool_use": auto_approve_tool},
- }
+ model="claude-haiku-4.5",
+ on_permission_request=auto_approve_permission,
+ on_user_input_request=handle_user_input,
+ hooks={"on_pre_tool_use": auto_approve_tool},
)
response = await session.send_and_wait(
diff --git a/test/scenarios/modes/default/python/main.py b/test/scenarios/modes/default/python/main.py
index ece50a662..3bb6e10a3 100644
--- a/test/scenarios/modes/default/python/main.py
+++ b/test/scenarios/modes/default/python/main.py
@@ -1,21 +1,20 @@
import asyncio
import os
+
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
- session = await client.create_session({
- "model": "claude-haiku-4.5",
- })
+ session = await client.create_session(model="claude-haiku-4.5")
- response = await session.send_and_wait("Use the grep tool to search for the word 'SDK' in README.md and show the matching lines.")
+ response = await session.send_and_wait(
+ "Use the grep tool to search for the word 'SDK' in README.md and show the matching lines."
+ )
if response:
print(f"Response: {response.data.content}")
diff --git a/test/scenarios/modes/minimal/python/main.py b/test/scenarios/modes/minimal/python/main.py
index 722c1e5e1..71811d377 100644
--- a/test/scenarios/modes/minimal/python/main.py
+++ b/test/scenarios/modes/minimal/python/main.py
@@ -1,26 +1,27 @@
import asyncio
import os
+
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
- session = await client.create_session({
- "model": "claude-haiku-4.5",
- "available_tools": [],
- "system_message": {
+ session = await client.create_session(
+ model="claude-haiku-4.5",
+ available_tools=[],
+ system_message={
"mode": "replace",
"content": "You have no tools. Respond with text only.",
},
- })
+ )
- response = await session.send_and_wait("Use the grep tool to search for 'SDK' in README.md.")
+ response = await session.send_and_wait(
+ "Use the grep tool to search for 'SDK' in README.md."
+ )
if response:
print(f"Response: {response.data.content}")
diff --git a/test/scenarios/prompts/attachments/python/main.py b/test/scenarios/prompts/attachments/python/main.py
index fdf259c6a..998770298 100644
--- a/test/scenarios/prompts/attachments/python/main.py
+++ b/test/scenarios/prompts/attachments/python/main.py
@@ -1,24 +1,21 @@
import asyncio
import os
+
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
SYSTEM_PROMPT = """You are a helpful assistant. Answer questions about attached files concisely."""
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
session = await client.create_session(
- {
- "model": "claude-haiku-4.5",
- "system_message": {"mode": "replace", "content": SYSTEM_PROMPT},
- "available_tools": [],
- }
+ model="claude-haiku-4.5",
+ system_message={"mode": "replace", "content": SYSTEM_PROMPT},
+ available_tools=[],
)
sample_file = os.path.join(os.path.dirname(__file__), "..", "sample-data.txt")
diff --git a/test/scenarios/prompts/reasoning-effort/python/main.py b/test/scenarios/prompts/reasoning-effort/python/main.py
index 122f44895..ae4b0264f 100644
--- a/test/scenarios/prompts/reasoning-effort/python/main.py
+++ b/test/scenarios/prompts/reasoning-effort/python/main.py
@@ -1,30 +1,27 @@
import asyncio
import os
+
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
- session = await client.create_session({
- "model": "claude-opus-4.6",
- "reasoning_effort": "low",
- "available_tools": [],
- "system_message": {
+ session = await client.create_session(
+ model="claude-opus-4.6",
+ reasoning_effort="low",
+ available_tools=[],
+ system_message={
"mode": "replace",
"content": "You are a helpful assistant. Answer concisely.",
},
- })
-
- response = await session.send_and_wait(
- "What is the capital of France?"
)
+ response = await session.send_and_wait("What is the capital of France?")
+
if response:
print("Reasoning effort: low")
print(f"Response: {response.data.content}")
diff --git a/test/scenarios/prompts/system-message/python/main.py b/test/scenarios/prompts/system-message/python/main.py
index b77c1e4a1..490347234 100644
--- a/test/scenarios/prompts/system-message/python/main.py
+++ b/test/scenarios/prompts/system-message/python/main.py
@@ -1,29 +1,24 @@
import asyncio
import os
+
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
PIRATE_PROMPT = """You are a pirate. Always respond in pirate speak. Say 'Arrr!' in every response. Use nautical terms and pirate slang throughout."""
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
session = await client.create_session(
- {
- "model": "claude-haiku-4.5",
- "system_message": {"mode": "replace", "content": PIRATE_PROMPT},
- "available_tools": [],
- }
+ model="claude-haiku-4.5",
+ system_message={"mode": "replace", "content": PIRATE_PROMPT},
+ available_tools=[],
)
- response = await session.send_and_wait(
- "What is the capital of France?"
- )
+ response = await session.send_and_wait("What is the capital of France?")
if response:
print(response.data.content)
diff --git a/test/scenarios/sessions/concurrent-sessions/python/main.py b/test/scenarios/sessions/concurrent-sessions/python/main.py
index a32dc5e10..beee2ba06 100644
--- a/test/scenarios/sessions/concurrent-sessions/python/main.py
+++ b/test/scenarios/sessions/concurrent-sessions/python/main.py
@@ -1,43 +1,34 @@
import asyncio
import os
+
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
PIRATE_PROMPT = "You are a pirate. Always say Arrr!"
ROBOT_PROMPT = "You are a robot. Always say BEEP BOOP!"
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
session1, session2 = await asyncio.gather(
client.create_session(
- {
- "model": "claude-haiku-4.5",
- "system_message": {"mode": "replace", "content": PIRATE_PROMPT},
- "available_tools": [],
- }
+ model="claude-haiku-4.5",
+ system_message={"mode": "replace", "content": PIRATE_PROMPT},
+ available_tools=[],
),
client.create_session(
- {
- "model": "claude-haiku-4.5",
- "system_message": {"mode": "replace", "content": ROBOT_PROMPT},
- "available_tools": [],
- }
+ model="claude-haiku-4.5",
+ system_message={"mode": "replace", "content": ROBOT_PROMPT},
+ available_tools=[],
),
)
response1, response2 = await asyncio.gather(
- session1.send_and_wait(
- "What is the capital of France?"
- ),
- session2.send_and_wait(
- "What is the capital of France?"
- ),
+ session1.send_and_wait("What is the capital of France?"),
+ session2.send_and_wait("What is the capital of France?"),
)
if response1:
diff --git a/test/scenarios/sessions/infinite-sessions/python/main.py b/test/scenarios/sessions/infinite-sessions/python/main.py
index 724dc155d..a41b2b4fa 100644
--- a/test/scenarios/sessions/infinite-sessions/python/main.py
+++ b/test/scenarios/sessions/infinite-sessions/python/main.py
@@ -1,29 +1,28 @@
import asyncio
import os
+
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
- session = await client.create_session({
- "model": "claude-haiku-4.5",
- "available_tools": [],
- "system_message": {
+ session = await client.create_session(
+ model="claude-haiku-4.5",
+ available_tools=[],
+ system_message={
"mode": "replace",
"content": "You are a helpful assistant. Answer concisely in one sentence.",
},
- "infinite_sessions": {
+ infinite_sessions={
"enabled": True,
"background_compaction_threshold": 0.80,
"buffer_exhaustion_threshold": 0.95,
},
- })
+ )
prompts = [
"What is the capital of France?",
diff --git a/test/scenarios/sessions/session-resume/python/main.py b/test/scenarios/sessions/session-resume/python/main.py
index ccb9c69f0..6d7ae02a2 100644
--- a/test/scenarios/sessions/session-resume/python/main.py
+++ b/test/scenarios/sessions/session-resume/python/main.py
@@ -1,28 +1,20 @@
import asyncio
import os
+
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
# 1. Create a session
- session = await client.create_session(
- {
- "model": "claude-haiku-4.5",
- "available_tools": [],
- }
- )
+ session = await client.create_session(model="claude-haiku-4.5", available_tools=[])
# 2. Send the secret word
- await session.send_and_wait(
- "Remember this: the secret word is PINEAPPLE."
- )
+ await session.send_and_wait("Remember this: the secret word is PINEAPPLE.")
# 3. Get the session ID (don't disconnect — resume needs the session to persist)
session_id = session.session_id
@@ -32,9 +24,7 @@ async def main():
print("Session resumed")
# 5. Ask for the secret word
- response = await resumed.send_and_wait(
- "What was the secret word I told you?"
- )
+ response = await resumed.send_and_wait("What was the secret word I told you?")
if response:
print(response.data.content)
diff --git a/test/scenarios/sessions/streaming/python/main.py b/test/scenarios/sessions/streaming/python/main.py
index e2312cd14..6c1c19f7b 100644
--- a/test/scenarios/sessions/streaming/python/main.py
+++ b/test/scenarios/sessions/streaming/python/main.py
@@ -1,22 +1,16 @@
import asyncio
import os
+
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
- session = await client.create_session(
- {
- "model": "claude-haiku-4.5",
- "streaming": True,
- }
- )
+ session = await client.create_session(model="claude-haiku-4.5", streaming=True)
chunk_count = 0
@@ -27,9 +21,7 @@ def on_event(event):
session.on(on_event)
- response = await session.send_and_wait(
- "What is the capital of France?"
- )
+ response = await session.send_and_wait("What is the capital of France?")
if response:
print(response.data.content)
diff --git a/test/scenarios/tools/custom-agents/python/main.py b/test/scenarios/tools/custom-agents/python/main.py
index bf6e3978c..aa5e254ae 100644
--- a/test/scenarios/tools/custom-agents/python/main.py
+++ b/test/scenarios/tools/custom-agents/python/main.py
@@ -1,7 +1,7 @@
import asyncio
import os
+
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
from copilot.tools import Tool
@@ -10,10 +10,9 @@ async def analyze_handler(args):
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
session = await client.create_session(
diff --git a/test/scenarios/tools/mcp-servers/python/main.py b/test/scenarios/tools/mcp-servers/python/main.py
index 2fa81b82d..80319e79a 100644
--- a/test/scenarios/tools/mcp-servers/python/main.py
+++ b/test/scenarios/tools/mcp-servers/python/main.py
@@ -1,14 +1,13 @@
import asyncio
import os
+
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
# MCP server config — demonstrates the configuration pattern.
@@ -16,7 +15,11 @@ async def main():
# Otherwise, runs without MCP tools as a build/integration test.
mcp_servers = {}
if os.environ.get("MCP_SERVER_CMD"):
- args = os.environ.get("MCP_SERVER_ARGS", "").split() if os.environ.get("MCP_SERVER_ARGS") else []
+ args = (
+ os.environ.get("MCP_SERVER_ARGS", "").split()
+ if os.environ.get("MCP_SERVER_ARGS")
+ else []
+ )
mcp_servers["example"] = {
"type": "stdio",
"command": os.environ["MCP_SERVER_CMD"],
@@ -36,9 +39,7 @@ async def main():
session = await client.create_session(session_config)
- response = await session.send_and_wait(
- "What is the capital of France?"
- )
+ response = await session.send_and_wait("What is the capital of France?")
if response:
print(response.data.content)
diff --git a/test/scenarios/tools/no-tools/python/main.py b/test/scenarios/tools/no-tools/python/main.py
index c3eeb6a17..61fa98ee1 100644
--- a/test/scenarios/tools/no-tools/python/main.py
+++ b/test/scenarios/tools/no-tools/python/main.py
@@ -1,7 +1,7 @@
import asyncio
import os
+
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
SYSTEM_PROMPT = """You are a minimal assistant with no tools available.
You cannot execute code, read files, edit files, search, or perform any actions.
@@ -10,23 +10,18 @@
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
session = await client.create_session(
- {
- "model": "claude-haiku-4.5",
- "system_message": {"mode": "replace", "content": SYSTEM_PROMPT},
- "available_tools": [],
- }
+ model="claude-haiku-4.5",
+ system_message={"mode": "replace", "content": SYSTEM_PROMPT},
+ available_tools=[],
)
- response = await session.send_and_wait(
- "Use the bash tool to run 'echo hello'."
- )
+ response = await session.send_and_wait("Use the bash tool to run 'echo hello'.")
if response:
print(response.data.content)
diff --git a/test/scenarios/tools/skills/python/main.py b/test/scenarios/tools/skills/python/main.py
index a6d6bf2c0..30b82fc1f 100644
--- a/test/scenarios/tools/skills/python/main.py
+++ b/test/scenarios/tools/skills/python/main.py
@@ -3,21 +3,19 @@
from pathlib import Path
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
-from copilot.session import PermissionRequestResult
+from copilot.generated.rpc import PermissionDecisionApproveOnce
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
skills_dir = str(Path(__file__).resolve().parent.parent / "sample-skills")
session = await client.create_session(
- on_permission_request=lambda _, __: PermissionRequestResult(kind="approve-once"),
+ on_permission_request=lambda _, __: PermissionDecisionApproveOnce(),
model="claude-haiku-4.5",
skill_directories=[skills_dir],
hooks={
diff --git a/test/scenarios/tools/tool-filtering/python/main.py b/test/scenarios/tools/tool-filtering/python/main.py
index 9da4ca571..711e8301e 100644
--- a/test/scenarios/tools/tool-filtering/python/main.py
+++ b/test/scenarios/tools/tool-filtering/python/main.py
@@ -1,24 +1,21 @@
import asyncio
import os
+
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
SYSTEM_PROMPT = """You are a helpful assistant. You have access to a limited set of tools. When asked about your tools, list exactly which tools you have available."""
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
session = await client.create_session(
- {
- "model": "claude-haiku-4.5",
- "system_message": {"mode": "replace", "content": SYSTEM_PROMPT},
- "available_tools": ["grep", "glob", "view"],
- }
+ model="claude-haiku-4.5",
+ system_message={"mode": "replace", "content": SYSTEM_PROMPT},
+ available_tools=["grep", "glob", "view"],
)
response = await session.send_and_wait(
diff --git a/test/scenarios/tools/tool-overrides/python/main.py b/test/scenarios/tools/tool-overrides/python/main.py
index 687933973..9aaaa9022 100644
--- a/test/scenarios/tools/tool-overrides/python/main.py
+++ b/test/scenarios/tools/tool-overrides/python/main.py
@@ -4,7 +4,6 @@
from pydantic import BaseModel, Field
from copilot import CopilotClient, define_tool
-from copilot.client import SubprocessConfig
from copilot.session import PermissionHandler
@@ -12,25 +11,28 @@ class GrepParams(BaseModel):
query: str = Field(description="Search query")
-@define_tool("grep", description="A custom grep implementation that overrides the built-in", overrides_built_in_tool=True)
+@define_tool(
+ "grep",
+ description="A custom grep implementation that overrides the built-in",
+ overrides_built_in_tool=True,
+)
def custom_grep(params: GrepParams) -> str:
return f"CUSTOM_GREP_RESULT: {params.query}"
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
session = await client.create_session(
- on_permission_request=PermissionHandler.approve_all, model="claude-haiku-4.5", tools=[custom_grep]
+ on_permission_request=PermissionHandler.approve_all,
+ model="claude-haiku-4.5",
+ tools=[custom_grep],
)
- response = await session.send_and_wait(
- "Use grep to search for the word 'hello'"
- )
+ response = await session.send_and_wait("Use grep to search for the word 'hello'")
if response:
print(response.data.content)
diff --git a/test/scenarios/tools/virtual-filesystem/python/main.py b/test/scenarios/tools/virtual-filesystem/python/main.py
index 57b197509..048ba1fd1 100644
--- a/test/scenarios/tools/virtual-filesystem/python/main.py
+++ b/test/scenarios/tools/virtual-filesystem/python/main.py
@@ -1,10 +1,11 @@
import asyncio
import os
-from copilot import CopilotClient, define_tool
-from copilot.client import SubprocessConfig
-from copilot.session import PermissionRequestResult
+
from pydantic import BaseModel, Field
+from copilot import CopilotClient, define_tool
+from copilot.generated.rpc import PermissionDecisionApproveOnce
+
# In-memory virtual filesystem
virtual_fs: dict[str, str] = {}
@@ -40,7 +41,7 @@ def list_files() -> str:
async def auto_approve_permission(request, invocation):
- return PermissionRequestResult(kind="approve-once")
+ return PermissionDecisionApproveOnce()
async def auto_approve_tool(input_data, invocation):
@@ -48,10 +49,9 @@ async def auto_approve_tool(input_data, invocation):
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
session = await client.create_session(
diff --git a/test/scenarios/transport/reconnect/python/main.py b/test/scenarios/transport/reconnect/python/main.py
index d1d4505a8..cc79f9721 100644
--- a/test/scenarios/transport/reconnect/python/main.py
+++ b/test/scenarios/transport/reconnect/python/main.py
@@ -1,23 +1,23 @@
import asyncio
import os
import sys
-from copilot import CopilotClient
-from copilot.client import ExternalServerConfig
+
+from copilot import CopilotClient, RuntimeConnection
async def main():
- client = CopilotClient(ExternalServerConfig(
- url=os.environ.get("COPILOT_CLI_URL", "localhost:3000"),
- ))
+ client = CopilotClient(
+ connection=RuntimeConnection.for_uri(
+ os.environ.get("COPILOT_CLI_URL", "localhost:3000"),
+ ),
+ )
try:
# First session
print("--- Session 1 ---")
- session1 = await client.create_session({"model": "claude-haiku-4.5"})
+ session1 = await client.create_session(model="claude-haiku-4.5")
- response1 = await session1.send_and_wait(
- "What is the capital of France?"
- )
+ response1 = await session1.send_and_wait("What is the capital of France?")
if response1 and response1.data.content:
print(response1.data.content)
@@ -30,11 +30,9 @@ async def main():
# Second session — tests that the server accepts new sessions
print("--- Session 2 ---")
- session2 = await client.create_session({"model": "claude-haiku-4.5"})
+ session2 = await client.create_session(model="claude-haiku-4.5")
- response2 = await session2.send_and_wait(
- "What is the capital of France?"
- )
+ response2 = await session2.send_and_wait("What is the capital of France?")
if response2 and response2.data.content:
print(response2.data.content)
diff --git a/test/scenarios/transport/stdio/python/main.py b/test/scenarios/transport/stdio/python/main.py
index 39ce2bb81..63c309995 100644
--- a/test/scenarios/transport/stdio/python/main.py
+++ b/test/scenarios/transport/stdio/python/main.py
@@ -1,21 +1,18 @@
import asyncio
import os
+
from copilot import CopilotClient
-from copilot.client import SubprocessConfig
async def main():
- client = CopilotClient(SubprocessConfig(
+ client = CopilotClient(
github_token=os.environ.get("GITHUB_TOKEN"),
- cli_path=os.environ.get("COPILOT_CLI_PATH"),
- ))
+ )
try:
- session = await client.create_session({"model": "claude-haiku-4.5"})
+ session = await client.create_session(model="claude-haiku-4.5")
- response = await session.send_and_wait(
- "What is the capital of France?"
- )
+ response = await session.send_and_wait("What is the capital of France?")
if response:
print(response.data.content)
diff --git a/test/scenarios/transport/tcp/python/main.py b/test/scenarios/transport/tcp/python/main.py
index b441bec51..1bf32b475 100644
--- a/test/scenarios/transport/tcp/python/main.py
+++ b/test/scenarios/transport/tcp/python/main.py
@@ -1,20 +1,20 @@
import asyncio
import os
-from copilot import CopilotClient
-from copilot.client import ExternalServerConfig
+
+from copilot import CopilotClient, RuntimeConnection
async def main():
- client = CopilotClient(ExternalServerConfig(
- url=os.environ.get("COPILOT_CLI_URL", "localhost:3000"),
- ))
+ client = CopilotClient(
+ connection=RuntimeConnection.for_uri(
+ os.environ.get("COPILOT_CLI_URL", "localhost:3000"),
+ ),
+ )
try:
- session = await client.create_session({"model": "claude-haiku-4.5"})
+ session = await client.create_session(model="claude-haiku-4.5")
- response = await session.send_and_wait(
- "What is the capital of France?"
- )
+ response = await session.send_and_wait("What is the capital of France?")
if response:
print(response.data.content)