diff --git a/docs/auth/byok.md b/docs/auth/byok.md
index 13ad8b055..abab53ae0 100644
--- a/docs/auth/byok.md
+++ b/docs/auth/byok.md
@@ -23,7 +23,7 @@ Azure AI Foundry (formerly Azure OpenAI) is a common BYOK deployment target for
```python
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
FOUNDRY_MODEL_URL = "https://your-resource.openai.azure.com/openai/v1/"
# Set FOUNDRY_API_KEY environment variable
@@ -32,14 +32,11 @@ async def main():
client = CopilotClient()
await client.start()
- session = await client.create_session({
- "model": "gpt-5.2-codex", # Your deployment name
- "provider": {
- "type": "openai",
- "base_url": FOUNDRY_MODEL_URL,
- "wire_api": "responses", # Use "completions" for older models
- "api_key": os.environ["FOUNDRY_API_KEY"],
- },
+ session = await client.create_session(PermissionHandler.approve_all, "gpt-5.2-codex", provider={
+ "type": "openai",
+ "base_url": FOUNDRY_MODEL_URL,
+ "wire_api": "responses", # Use "completions" for older models
+ "api_key": os.environ["FOUNDRY_API_KEY"],
})
done = asyncio.Event()
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 56c6a9c46..a4482640d 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -129,13 +129,13 @@ Create `main.py`:
```python
import asyncio
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
async def main():
client = CopilotClient()
await client.start()
- session = await client.create_session({"model": "gpt-4.1"})
+ session = await client.create_session(PermissionHandler.approve_all, "gpt-4.1")
response = await session.send_and_wait({"prompt": "What is 2 + 2?"})
print(response.data.content)
@@ -274,17 +274,14 @@ Update `main.py`:
```python
import asyncio
import sys
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
from copilot.generated.session_events import SessionEventType
async def main():
client = CopilotClient()
await client.start()
- session = await client.create_session({
- "model": "gpt-4.1",
- "streaming": True,
- })
+ session = await client.create_session(PermissionHandler.approve_all, "gpt-4.1", streaming=True)
# Listen for response chunks
def handle_event(event):
@@ -565,7 +562,7 @@ Update `main.py`:
import asyncio
import random
import sys
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
from copilot.tools import define_tool
from copilot.generated.session_events import SessionEventType
from pydantic import BaseModel, Field
@@ -588,11 +585,7 @@ async def main():
client = CopilotClient()
await client.start()
- session = await client.create_session({
- "model": "gpt-4.1",
- "streaming": True,
- "tools": [get_weather],
- })
+ session = await client.create_session(PermissionHandler.approve_all, "gpt-4.1", streaming=True, tools=[get_weather])
def handle_event(event):
if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA:
@@ -837,7 +830,7 @@ Create `weather_assistant.py`:
import asyncio
import random
import sys
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
from copilot.tools import define_tool
from copilot.generated.session_events import SessionEventType
from pydantic import BaseModel, Field
@@ -857,11 +850,7 @@ async def main():
client = CopilotClient()
await client.start()
- session = await client.create_session({
- "model": "gpt-4.1",
- "streaming": True,
- "tools": [get_weather],
- })
+ session = await client.create_session(PermissionHandler.approve_all, "gpt-4.1", streaming=True, tools=[get_weather])
def handle_event(event):
if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA:
@@ -1218,7 +1207,7 @@ client = CopilotClient({
await client.start()
# Use the client normally
-session = await client.create_session({"on_permission_request": PermissionHandler.approve_all})
+session = await client.create_session(PermissionHandler.approve_all)
# ...
```
diff --git a/docs/guides/session-persistence.md b/docs/guides/session-persistence.md
index 527f5ecc7..de58ecd35 100644
--- a/docs/guides/session-persistence.md
+++ b/docs/guides/session-persistence.md
@@ -46,16 +46,13 @@ await session.sendAndWait({ prompt: "Analyze my codebase" });
### Python
```python
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
client = CopilotClient()
await client.start()
# Create a session with a meaningful ID
-session = await client.create_session({
- "session_id": "user-123-task-456",
- "model": "gpt-5.2-codex",
-})
+session = await client.create_session(PermissionHandler.approve_all, "gpt-5.2-codex", session_id="user-123-task-456")
# Do some work...
await session.send_and_wait({"prompt": "Analyze my codebase"})
diff --git a/docs/guides/setup/azure-managed-identity.md b/docs/guides/setup/azure-managed-identity.md
index bfafc6f91..c0296fe28 100644
--- a/docs/guides/setup/azure-managed-identity.md
+++ b/docs/guides/setup/azure-managed-identity.md
@@ -42,7 +42,7 @@ import asyncio
import os
from azure.identity import DefaultAzureCredential
-from copilot import CopilotClient, ProviderConfig, SessionConfig
+from copilot import CopilotClient, PermissionHandler
COGNITIVE_SERVICES_SCOPE = "https://cognitiveservices.azure.com/.default"
@@ -58,15 +58,14 @@ async def main():
await client.start()
session = await client.create_session(
- SessionConfig(
- model="gpt-4.1",
- provider=ProviderConfig(
- type="openai",
- base_url=f"{foundry_url.rstrip('/')}/openai/v1/",
- bearer_token=token, # Short-lived bearer token
- wire_api="responses",
- ),
- )
+ PermissionHandler.approve_all,
+ "gpt-4.1",
+ provider={
+ "type": "openai",
+ "base_url": f"{foundry_url.rstrip('/')}/openai/v1/",
+ "bearer_token": token, # Short-lived bearer token
+ "wire_api": "responses",
+ },
)
response = await session.send_and_wait({"prompt": "Hello from Managed Identity!"})
@@ -84,7 +83,7 @@ Bearer tokens expire (typically after ~1 hour). For servers or long-running agen
```python
from azure.identity import DefaultAzureCredential
-from copilot import CopilotClient, ProviderConfig, SessionConfig
+from copilot import CopilotClient, PermissionHandler
COGNITIVE_SERVICES_SCOPE = "https://cognitiveservices.azure.com/.default"
@@ -98,24 +97,21 @@ class ManagedIdentityCopilotAgent:
self.credential = DefaultAzureCredential()
self.client = CopilotClient()
- def _get_session_config(self) -> SessionConfig:
- """Build a SessionConfig with a fresh bearer token."""
+ def _get_provider_config(self) -> dict:
+ """Build a provider config dict with a fresh bearer token."""
token = self.credential.get_token(COGNITIVE_SERVICES_SCOPE).token
- return SessionConfig(
- model=self.model,
- provider=ProviderConfig(
- type="openai",
- base_url=f"{self.foundry_url}/openai/v1/",
- bearer_token=token,
- wire_api="responses",
- ),
- )
+ return {
+ "type": "openai",
+ "base_url": f"{self.foundry_url}/openai/v1/",
+ "bearer_token": token,
+ "wire_api": "responses",
+ }
async def chat(self, prompt: str) -> str:
"""Send a prompt and return the response text."""
# Fresh token for each session
- config = self._get_session_config()
- session = await self.client.create_session(config)
+ provider = self._get_provider_config()
+ session = await self.client.create_session(PermissionHandler.approve_all, self.model, provider=provider)
response = await session.send_and_wait({"prompt": prompt})
await session.destroy()
diff --git a/docs/guides/setup/backend-services.md b/docs/guides/setup/backend-services.md
index c9bc13f8d..81b552db5 100644
--- a/docs/guides/setup/backend-services.md
+++ b/docs/guides/setup/backend-services.md
@@ -111,17 +111,14 @@ res.json({ content: response?.data.content });
Python
```python
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
client = CopilotClient({
"cli_url": "localhost:4321",
})
await client.start()
-session = await client.create_session({
- "session_id": f"user-{user_id}-{int(time.time())}",
- "model": "gpt-4.1",
-})
+session = await client.create_session(PermissionHandler.approve_all, "gpt-4.1", session_id=f"user-{user_id}-{int(time.time())}")
response = await session.send_and_wait({"prompt": message})
```
diff --git a/docs/guides/setup/bundled-cli.md b/docs/guides/setup/bundled-cli.md
index 6daf57b56..2429d5104 100644
--- a/docs/guides/setup/bundled-cli.md
+++ b/docs/guides/setup/bundled-cli.md
@@ -85,7 +85,7 @@ await client.stop();
Python
```python
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
from pathlib import Path
client = CopilotClient({
@@ -93,7 +93,7 @@ client = CopilotClient({
})
await client.start()
-session = await client.create_session({"model": "gpt-4.1"})
+session = await client.create_session(PermissionHandler.approve_all, "gpt-4.1")
response = await session.send_and_wait({"prompt": "Hello!"})
print(response.data.content)
diff --git a/docs/guides/setup/byok.md b/docs/guides/setup/byok.md
index 5b8b8a460..2ca844259 100644
--- a/docs/guides/setup/byok.md
+++ b/docs/guides/setup/byok.md
@@ -93,18 +93,15 @@ await client.stop();
```python
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
client = CopilotClient()
await client.start()
-session = await client.create_session({
- "model": "gpt-4.1",
- "provider": {
- "type": "openai",
- "base_url": "https://api.openai.com/v1",
- "api_key": os.environ["OPENAI_API_KEY"],
- },
+session = await client.create_session(PermissionHandler.approve_all, "gpt-4.1", provider={
+ "type": "openai",
+ "base_url": "https://api.openai.com/v1",
+ "api_key": os.environ["OPENAI_API_KEY"],
})
response = await session.send_and_wait({"prompt": "Hello!"})
diff --git a/docs/guides/setup/github-oauth.md b/docs/guides/setup/github-oauth.md
index 07251c8fb..94d859e3a 100644
--- a/docs/guides/setup/github-oauth.md
+++ b/docs/guides/setup/github-oauth.md
@@ -145,7 +145,7 @@ const response = await session.sendAndWait({ prompt: "Hello!" });
Python
```python
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
def create_client_for_user(user_token: str) -> CopilotClient:
return CopilotClient({
@@ -157,10 +157,7 @@ def create_client_for_user(user_token: str) -> CopilotClient:
client = create_client_for_user("gho_user_access_token")
await client.start()
-session = await client.create_session({
- "session_id": f"user-{user_id}-session",
- "model": "gpt-4.1",
-})
+session = await client.create_session(PermissionHandler.approve_all, "gpt-4.1", session_id=f"user-{user_id}-session")
response = await session.send_and_wait({"prompt": "Hello!"})
```
diff --git a/docs/guides/setup/local-cli.md b/docs/guides/setup/local-cli.md
index a5fa906b8..4923d6710 100644
--- a/docs/guides/setup/local-cli.md
+++ b/docs/guides/setup/local-cli.md
@@ -51,12 +51,12 @@ await client.stop();
Python
```python
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
client = CopilotClient()
await client.start()
-session = await client.create_session({"model": "gpt-4.1"})
+session = await client.create_session(PermissionHandler.approve_all, "gpt-4.1")
response = await session.send_and_wait({"prompt": "Hello!"})
print(response.data.content)
diff --git a/docs/guides/skills.md b/docs/guides/skills.md
index b2ea3ae7a..d87341b79 100644
--- a/docs/guides/skills.md
+++ b/docs/guides/skills.md
@@ -48,14 +48,14 @@ async def main():
client = CopilotClient()
await client.start()
- session = await client.create_session({
- "model": "gpt-4.1",
- "skill_directories": [
+ session = await client.create_session(
+ lambda req, inv: {"kind": "approved"},
+ "gpt-4.1",
+ skill_directories=[
"./skills/code-review",
"./skills/documentation",
],
- "on_permission_request": lambda req: {"kind": "approved"},
- })
+ )
# Copilot now has access to skills in those directories
await session.send_and_wait({"prompt": "Review this code for security issues"})
@@ -159,10 +159,13 @@ const session = await client.createSession({
Python
```python
-session = await client.create_session({
- "skill_directories": ["./skills"],
- "disabled_skills": ["experimental-feature", "deprecated-tool"],
-})
+from copilot import PermissionHandler
+
+session = await client.create_session(
+ PermissionHandler.approve_all,
+ skill_directories=["./skills"],
+ disabled_skills=["experimental-feature", "deprecated-tool"],
+)
```
diff --git a/docs/hooks/error-handling.md b/docs/hooks/error-handling.md
index 0f705868d..b8e15f102 100644
--- a/docs/hooks/error-handling.md
+++ b/docs/hooks/error-handling.md
@@ -107,15 +107,15 @@ const session = await client.createSession({
Python
```python
+from copilot import PermissionHandler
+
async def on_error_occurred(input_data, invocation):
print(f"[{invocation['session_id']}] Error: {input_data['error']}")
print(f" Context: {input_data['errorContext']}")
print(f" Recoverable: {input_data['recoverable']}")
return None
-session = await client.create_session({
- "hooks": {"on_error_occurred": on_error_occurred}
-})
+session = await client.create_session(PermissionHandler.approve_all, hooks={"on_error_occurred": on_error_occurred})
```
diff --git a/docs/hooks/overview.md b/docs/hooks/overview.md
index a51ef0464..e54a57237 100644
--- a/docs/hooks/overview.md
+++ b/docs/hooks/overview.md
@@ -53,7 +53,7 @@ const session = await client.createSession({
Python
```python
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
async def main():
client = CopilotClient()
@@ -70,13 +70,11 @@ async def main():
async def on_session_start(input_data, invocation):
return {"additionalContext": "User prefers concise answers."}
- session = await client.create_session({
- "hooks": {
+ session = await client.create_session(PermissionHandler.approve_all, hooks={
"on_pre_tool_use": on_pre_tool_use,
"on_post_tool_use": on_post_tool_use,
"on_session_start": on_session_start,
- }
- })
+ })
```
diff --git a/docs/hooks/post-tool-use.md b/docs/hooks/post-tool-use.md
index 0021e20a0..797458961 100644
--- a/docs/hooks/post-tool-use.md
+++ b/docs/hooks/post-tool-use.md
@@ -106,15 +106,15 @@ const session = await client.createSession({
Python
```python
+from copilot import PermissionHandler
+
async def on_post_tool_use(input_data, invocation):
print(f"[{invocation['session_id']}] Tool: {input_data['toolName']}")
print(f" Args: {input_data['toolArgs']}")
print(f" Result: {input_data['toolResult']}")
return None # Pass through unchanged
-session = await client.create_session({
- "hooks": {"on_post_tool_use": on_post_tool_use}
-})
+session = await client.create_session(PermissionHandler.approve_all, hooks={"on_post_tool_use": on_post_tool_use})
```
diff --git a/docs/hooks/pre-tool-use.md b/docs/hooks/pre-tool-use.md
index ac12df4fa..7fb369cc6 100644
--- a/docs/hooks/pre-tool-use.md
+++ b/docs/hooks/pre-tool-use.md
@@ -114,14 +114,14 @@ const session = await client.createSession({
Python
```python
+from copilot import PermissionHandler
+
async def on_pre_tool_use(input_data, invocation):
print(f"[{invocation['session_id']}] Calling {input_data['toolName']}")
print(f" Args: {input_data['toolArgs']}")
return {"permissionDecision": "allow"}
-session = await client.create_session({
- "hooks": {"on_pre_tool_use": on_pre_tool_use}
-})
+session = await client.create_session(PermissionHandler.approve_all, hooks={"on_pre_tool_use": on_pre_tool_use})
```
diff --git a/docs/hooks/session-lifecycle.md b/docs/hooks/session-lifecycle.md
index 74f4666f4..50a6b3ba2 100644
--- a/docs/hooks/session-lifecycle.md
+++ b/docs/hooks/session-lifecycle.md
@@ -113,6 +113,8 @@ Package manager: ${projectInfo.packageManager}
Python
```python
+from copilot import PermissionHandler
+
async def on_session_start(input_data, invocation):
print(f"Session {invocation['session_id']} started ({input_data['source']})")
@@ -126,9 +128,7 @@ Package manager: {project_info['packageManager']}
""".strip()
}
-session = await client.create_session({
- "hooks": {"on_session_start": on_session_start}
-})
+session = await client.create_session(PermissionHandler.approve_all, hooks={"on_session_start": on_session_start})
```
@@ -309,6 +309,8 @@ const session = await client.createSession({
Python
```python
+from copilot import PermissionHandler
+
session_start_times = {}
async def on_session_start(input_data, invocation):
@@ -328,12 +330,10 @@ async def on_session_end(input_data, invocation):
session_start_times.pop(invocation["session_id"], None)
return None
-session = await client.create_session({
- "hooks": {
+session = await client.create_session(PermissionHandler.approve_all, hooks={
"on_session_start": on_session_start,
"on_session_end": on_session_end,
- }
-})
+ })
```
diff --git a/docs/hooks/user-prompt-submitted.md b/docs/hooks/user-prompt-submitted.md
index 3205b95cd..4a2bda0ed 100644
--- a/docs/hooks/user-prompt-submitted.md
+++ b/docs/hooks/user-prompt-submitted.md
@@ -102,13 +102,13 @@ const session = await client.createSession({
Python
```python
+from copilot import PermissionHandler
+
async def on_user_prompt_submitted(input_data, invocation):
print(f"[{invocation['session_id']}] User: {input_data['prompt']}")
return None
-session = await client.create_session({
- "hooks": {"on_user_prompt_submitted": on_user_prompt_submitted}
-})
+session = await client.create_session(PermissionHandler.approve_all, hooks={"on_user_prompt_submitted": on_user_prompt_submitted})
```
diff --git a/docs/mcp/overview.md b/docs/mcp/overview.md
index aa2fba668..637079c78 100644
--- a/docs/mcp/overview.md
+++ b/docs/mcp/overview.md
@@ -59,32 +59,29 @@ const session = await client.createSession({
```python
import asyncio
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
async def main():
client = CopilotClient()
await client.start()
- session = await client.create_session({
- "model": "gpt-5",
- "mcp_servers": {
- # Local MCP server (stdio)
- "my-local-server": {
- "type": "local",
- "command": "python",
- "args": ["./mcp_server.py"],
- "env": {"DEBUG": "true"},
- "cwd": "./servers",
- "tools": ["*"],
- "timeout": 30000,
- },
- # Remote MCP server (HTTP)
- "github": {
- "type": "http",
- "url": "https://api.githubcopilot.com/mcp/",
- "headers": {"Authorization": "Bearer ${TOKEN}"},
- "tools": ["*"],
- },
+ session = await client.create_session(PermissionHandler.approve_all, "gpt-5", mcp_servers={
+ # Local MCP server (stdio)
+ "my-local-server": {
+ "type": "local",
+ "command": "python",
+ "args": ["./mcp_server.py"],
+ "env": {"DEBUG": "true"},
+ "cwd": "./servers",
+ "tools": ["*"],
+ "timeout": 30000,
+ },
+ # Remote MCP server (HTTP)
+ "github": {
+ "type": "http",
+ "url": "https://api.githubcopilot.com/mcp/",
+ "headers": {"Authorization": "Bearer ${TOKEN}"},
+ "tools": ["*"],
},
})
diff --git a/python/README.md b/python/README.md
index aa82e0c34..867af45f0 100644
--- a/python/README.md
+++ b/python/README.md
@@ -25,7 +25,7 @@ python chat.py
```python
import asyncio
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
async def main():
# Create and start client
@@ -33,7 +33,7 @@ async def main():
await client.start()
# Create a session
- session = await client.create_session({"model": "gpt-5"})
+ session = await client.create_session(PermissionHandler.approve_all, "gpt-5")
# Wait for response using session.idle event
done = asyncio.Event()
@@ -80,7 +80,7 @@ client = CopilotClient({
})
await client.start()
-session = await client.create_session({"model": "gpt-5"})
+session = await client.create_session(PermissionHandler.approve_all, "gpt-5")
def on_event(event):
print(f"Event: {event['type']}")
@@ -107,18 +107,31 @@ await client.stop()
- `github_token` (str): GitHub token for authentication. When provided, takes priority over other auth methods.
- `use_logged_in_user` (bool): Whether to use logged-in user for authentication (default: True, but False when `github_token` is provided). Cannot be used with `cli_url`.
-**SessionConfig Options (for `create_session`):**
+**`create_session` Parameters:**
-- `model` (str): Model to use ("gpt-5", "claude-sonnet-4.5", etc.). **Required when using custom provider.**
+- `on_permission_request` (callable): **Required.** Handler for permission requests from the server.
+- `model` (str): Model to use ("gpt-5", "claude-sonnet-4.5", etc.).
+
+The parameters below are keyword-only:
+
+- `session_id` (str): Custom session ID for resuming or identifying sessions.
+- `client_name` (str): Client name to identify the application using the SDK. Included in the User-Agent header for API requests.
- `reasoning_effort` (str): Reasoning effort level for models that support it ("low", "medium", "high", "xhigh"). Use `list_models()` to check which models support this option.
-- `session_id` (str): Custom session ID
-- `tools` (list): Custom tools exposed to the CLI
-- `system_message` (dict): System message configuration
-- `streaming` (bool): Enable streaming delta events
-- `provider` (dict): Custom API provider configuration (BYOK). See [Custom Providers](#custom-providers) section.
-- `infinite_sessions` (dict): Automatic context compaction configuration
+- `tools` (list): Custom tools exposed to the CLI.
+- `system_message` (dict): System message configuration.
+- `available_tools` (list[str]): List of tool names to allow. Takes precedence over `excluded_tools`.
+- `excluded_tools` (list[str]): List of tool names to disable. Ignored if `available_tools` is set.
- `on_user_input_request` (callable): Handler for user input requests from the agent (enables ask_user tool). See [User Input Requests](#user-input-requests) section.
- `hooks` (dict): Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section.
+- `working_directory` (str): Working directory for the session. Tool operations will be relative to this directory.
+- `provider` (dict): Custom API provider configuration (BYOK). See [Custom Providers](#custom-providers) section.
+- `streaming` (bool): Enable streaming delta events.
+- `mcp_servers` (dict): MCP server configurations for the session.
+- `custom_agents` (list): Custom agent configurations for the session.
+- `config_dir` (str): Override the default configuration directory location.
+- `skill_directories` (list[str]): Directories to load skills from.
+- `disabled_skills` (list[str]): List of skill names to disable.
+- `infinite_sessions` (dict): Automatic context compaction configuration.
**Session Lifecycle Methods:**
@@ -155,7 +168,7 @@ Define tools with automatic JSON schema generation using the `@define_tool` deco
```python
from pydantic import BaseModel, Field
-from copilot import CopilotClient, define_tool
+from copilot import CopilotClient, define_tool, PermissionHandler
class LookupIssueParams(BaseModel):
id: str = Field(description="Issue identifier")
@@ -165,10 +178,11 @@ async def lookup_issue(params: LookupIssueParams) -> str:
issue = await fetch_issue(params.id)
return issue.summary
-session = await client.create_session({
- "model": "gpt-5",
- "tools": [lookup_issue],
-})
+session = await client.create_session(
+ PermissionHandler.approve_all,
+ "gpt-5",
+ tools=[lookup_issue],
+)
```
> **Note:** When using `from __future__ import annotations`, define Pydantic models at module level (not inside functions).
@@ -178,7 +192,7 @@ session = await client.create_session({
For users who prefer manual schema definition:
```python
-from copilot import CopilotClient, Tool
+from copilot import CopilotClient, Tool, PermissionHandler
async def lookup_issue(invocation):
issue_id = invocation["arguments"]["id"]
@@ -189,9 +203,10 @@ async def lookup_issue(invocation):
"sessionLog": f"Fetched issue {issue_id}",
}
-session = await client.create_session({
- "model": "gpt-5",
- "tools": [
+session = await client.create_session(
+ PermissionHandler.approve_all,
+ "gpt-5",
+ tools=[
Tool(
name="lookup_issue",
description="Fetch issue details from our tracker",
@@ -205,7 +220,7 @@ session = await client.create_session({
handler=lookup_issue,
)
],
-})
+)
```
The SDK automatically handles `tool.call`, executes your handler (sync or async), and responds with the final result when the tool completes.
@@ -238,16 +253,17 @@ Enable streaming to receive assistant response chunks as they're generated:
```python
import asyncio
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
async def main():
client = CopilotClient()
await client.start()
- session = await client.create_session({
- "model": "gpt-5",
- "streaming": True
- })
+ session = await client.create_session(
+ PermissionHandler.approve_all,
+ "gpt-5",
+ streaming=True,
+ )
# Use asyncio.Event to wait for completion
done = asyncio.Event()
@@ -298,27 +314,29 @@ By default, sessions use **infinite sessions** which automatically manage contex
```python
# Default: infinite sessions enabled with default thresholds
-session = await client.create_session({"model": "gpt-5"})
+session = await client.create_session(PermissionHandler.approve_all, "gpt-5")
# Access the workspace path for checkpoints and files
print(session.workspace_path)
# => ~/.copilot/session-state/{session_id}/
# Custom thresholds
-session = await client.create_session({
- "model": "gpt-5",
- "infinite_sessions": {
+session = await client.create_session(
+ PermissionHandler.approve_all,
+ "gpt-5",
+ infinite_sessions={
"enabled": True,
"background_compaction_threshold": 0.80, # Start compacting at 80% context usage
"buffer_exhaustion_threshold": 0.95, # Block at 95% until compaction completes
},
-})
+)
# Disable infinite sessions
-session = await client.create_session({
- "model": "gpt-5",
- "infinite_sessions": {"enabled": False},
-})
+session = await client.create_session(
+ PermissionHandler.approve_all,
+ "gpt-5",
+ infinite_sessions={"enabled": False},
+)
```
When enabled, sessions emit compaction events:
@@ -342,14 +360,15 @@ The SDK supports custom OpenAI-compatible API providers (BYOK - Bring Your Own K
**Example with Ollama:**
```python
-session = await client.create_session({
- "model": "deepseek-coder-v2:16b", # Required when using custom provider
- "provider": {
+session = await client.create_session(
+ PermissionHandler.approve_all,
+ "deepseek-coder-v2:16b", # Model to use with the custom provider
+ provider={
"type": "openai",
"base_url": "http://localhost:11434/v1", # Ollama endpoint
# api_key not required for Ollama
},
-})
+)
await session.send({"prompt": "Hello!"})
```
@@ -359,14 +378,15 @@ await session.send({"prompt": "Hello!"})
```python
import os
-session = await client.create_session({
- "model": "gpt-4",
- "provider": {
+session = await client.create_session(
+ PermissionHandler.approve_all,
+ "gpt-4",
+ provider={
"type": "openai",
"base_url": "https://my-api.example.com/v1",
"api_key": os.environ["MY_API_KEY"],
},
-})
+)
```
**Example with Azure OpenAI:**
@@ -374,9 +394,10 @@ session = await client.create_session({
```python
import os
-session = await client.create_session({
- "model": "gpt-4",
- "provider": {
+session = await client.create_session(
+ PermissionHandler.approve_all,
+ "gpt-4",
+ provider={
"type": "azure", # Must be "azure" for Azure endpoints, NOT "openai"
"base_url": "https://my-resource.openai.azure.com", # Just the host, no path
"api_key": os.environ["AZURE_OPENAI_KEY"],
@@ -384,11 +405,10 @@ session = await client.create_session({
"api_version": "2024-10-21",
},
},
-})
+)
```
> **Important notes:**
-> - When using a custom provider, the `model` parameter is **required**. The SDK will throw an error if no model is specified.
> - For Azure OpenAI endpoints (`*.openai.azure.com`), you **must** use `type: "azure"`, not `type: "openai"`.
> - The `base_url` should be just the host (e.g., `https://my-resource.openai.azure.com`). Do **not** include `/openai/v1` in the URL - the SDK handles path construction automatically.
@@ -401,21 +421,22 @@ async def handle_user_input(request, invocation):
# request["question"] - The question to ask
# request.get("choices") - Optional list of choices for multiple choice
# request.get("allowFreeform", True) - Whether freeform input is allowed
-
+
print(f"Agent asks: {request['question']}")
if request.get("choices"):
print(f"Choices: {', '.join(request['choices'])}")
-
+
# Return the user's response
return {
"answer": "User's answer here",
"wasFreeform": True, # Whether the answer was freeform (not from choices)
}
-session = await client.create_session({
- "model": "gpt-5",
- "on_user_input_request": handle_user_input,
-})
+session = await client.create_session(
+ PermissionHandler.approve_all,
+ "gpt-5",
+ on_user_input_request=handle_user_input,
+)
```
## Session Hooks
@@ -459,9 +480,10 @@ async def on_error_occurred(input, invocation):
"errorHandling": "retry", # "retry", "skip", or "abort"
}
-session = await client.create_session({
- "model": "gpt-5",
- "hooks": {
+session = await client.create_session(
+ PermissionHandler.approve_all,
+ "gpt-5",
+ hooks={
"on_pre_tool_use": on_pre_tool_use,
"on_post_tool_use": on_post_tool_use,
"on_user_prompt_submitted": on_user_prompt_submitted,
@@ -469,7 +491,7 @@ session = await client.create_session({
"on_session_end": on_session_end,
"on_error_occurred": on_error_occurred,
},
-})
+)
```
**Available hooks:**
diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py
index f5f7ed0b1..d65c32b2d 100644
--- a/python/copilot/__init__.py
+++ b/python/copilot/__init__.py
@@ -27,7 +27,6 @@
PingResponse,
ProviderConfig,
ResumeSessionConfig,
- SessionConfig,
SessionContext,
SessionEvent,
SessionListFilter,
@@ -63,7 +62,6 @@
"PingResponse",
"ProviderConfig",
"ResumeSessionConfig",
- "SessionConfig",
"SessionContext",
"SessionEvent",
"SessionListFilter",
diff --git a/python/copilot/client.py b/python/copilot/client.py
index 774569afb..043d1c00c 100644
--- a/python/copilot/client.py
+++ b/python/copilot/client.py
@@ -5,10 +5,10 @@
to the Copilot CLI server and provides session management capabilities.
Example:
- >>> from copilot import CopilotClient
+ >>> from copilot import CopilotClient, PermissionHandler
>>>
>>> async with CopilotClient() as client:
- ... session = await client.create_session()
+ ... session = await client.create_session(PermissionHandler.approve_all)
... await session.send({"prompt": "Hello!"})
"""
@@ -34,20 +34,27 @@
CustomAgentConfig,
GetAuthStatusResponse,
GetStatusResponse,
+ InfiniteSessionConfig,
+ MCPServerConfig,
ModelInfo,
PingResponse,
ProviderConfig,
+ ReasoningEffort,
ResumeSessionConfig,
- SessionConfig,
+ SessionHooks,
SessionLifecycleEvent,
SessionLifecycleEventType,
SessionLifecycleHandler,
SessionListFilter,
SessionMetadata,
StopError,
+ SystemMessageConfig,
+ Tool,
ToolHandler,
ToolInvocation,
ToolResult,
+ UserInputHandler,
+ _PermissionHandlerFn,
)
@@ -417,7 +424,30 @@ async def force_stop(self) -> None:
if not self._is_external_server:
self._actual_port = None
- async def create_session(self, config: SessionConfig) -> CopilotSession:
+ async def create_session(
+ self,
+ on_permission_request: _PermissionHandlerFn,
+ model: str | None = None,
+ *,
+ session_id: str | None = None,
+ client_name: str | None = None,
+ reasoning_effort: ReasoningEffort | None = None,
+ tools: list[Tool] | None = None,
+ system_message: SystemMessageConfig | None = None,
+ available_tools: list[str] | None = None,
+ excluded_tools: list[str] | None = None,
+ on_user_input_request: UserInputHandler | None = None,
+ hooks: SessionHooks | None = None,
+ working_directory: str | None = None,
+ provider: ProviderConfig | None = None,
+ streaming: bool | None = None,
+ mcp_servers: dict[str, MCPServerConfig] | None = None,
+ custom_agents: list[CustomAgentConfig] | None = None,
+ config_dir: str | None = None,
+ skill_directories: list[str] | None = None,
+ disabled_skills: list[str] | None = None,
+ infinite_sessions: InfiniteSessionConfig | None = None,
+ ) -> CopilotSession:
"""
Create a new conversation session with the Copilot CLI.
@@ -426,8 +456,26 @@ async def create_session(self, config: SessionConfig) -> CopilotSession:
automatically start the connection.
Args:
- config: Optional configuration for the session, including model selection,
- custom tools, system messages, and more.
+ on_permission_request: Handler for permission requests from the server.
+ model: Model to use for this session.
+ session_id: Custom session ID.
+ client_name: Client name to identify the application using the SDK.
+ reasoning_effort: Reasoning effort level ("low", "medium", "high", "xhigh").
+ tools: Custom tools exposed to the CLI.
+ system_message: System message configuration.
+ available_tools: List of tool names to allow (takes precedence over excluded_tools).
+ excluded_tools: List of tool names to disable (ignored if available_tools is set).
+ on_user_input_request: Handler for user input requests (enables ask_user tool).
+ hooks: Hook handlers for intercepting session lifecycle events.
+ working_directory: Working directory for the session.
+ provider: Custom provider configuration (BYOK - Bring Your Own Key).
+ streaming: Enable streaming of assistant message and reasoning chunks.
+ mcp_servers: MCP server configurations for the session.
+ custom_agents: Custom agent configurations for the session.
+ config_dir: Override the default configuration directory location.
+ skill_directories: Directories to load skills from.
+ disabled_skills: List of skill names to disable.
+ infinite_sessions: Infinite session configuration for persistent workspaces.
Returns:
A :class:`CopilotSession` instance for the new session.
@@ -436,34 +484,28 @@ async def create_session(self, config: SessionConfig) -> CopilotSession:
RuntimeError: If the client is not connected and auto_start is disabled.
Example:
- >>> # Basic session
- >>> config = {"on_permission_request": PermissionHandler.approve_all}
- >>> session = await client.create_session(config)
+ >>> session = await client.create_session(PermissionHandler.approve_all)
>>>
>>> # Session with model and streaming
- >>> session = await client.create_session({
- ... "on_permission_request": PermissionHandler.approve_all,
- ... "model": "gpt-4",
- ... "streaming": True
- ... })
+ >>> session = await client.create_session(
+ ... PermissionHandler.approve_all,
+ ... "gpt-4",
+ ... streaming=True,
+ ... )
"""
+ if not on_permission_request or not callable(on_permission_request):
+ raise ValueError(
+ "A valid on_permission_request handler is required. "
+ "Use PermissionHandler.approve_all or provide a custom handler."
+ )
+
if not self._client:
if self.options["auto_start"]:
await self.start()
else:
raise RuntimeError("Client not connected. Call start() first.")
- cfg = config
-
- if not cfg.get("on_permission_request"):
- raise ValueError(
- "An on_permission_request handler is required when creating a session. "
- "For example, to allow all permissions, use "
- '{"on_permission_request": PermissionHandler.approve_all}.'
- )
-
tool_defs = []
- tools = cfg.get("tools")
if tools:
for tool in tools:
definition = {
@@ -475,89 +517,60 @@ async def create_session(self, config: SessionConfig) -> CopilotSession:
tool_defs.append(definition)
payload: dict[str, Any] = {}
- if cfg.get("model"):
- payload["model"] = cfg["model"]
- if cfg.get("session_id"):
- payload["sessionId"] = cfg["session_id"]
- if cfg.get("client_name"):
- payload["clientName"] = cfg["client_name"]
- if cfg.get("reasoning_effort"):
- payload["reasoningEffort"] = cfg["reasoning_effort"]
+ if model:
+ payload["model"] = model
+ if session_id:
+ payload["sessionId"] = session_id
+ if client_name:
+ payload["clientName"] = client_name
+ if reasoning_effort:
+ payload["reasoningEffort"] = reasoning_effort
if tool_defs:
payload["tools"] = tool_defs
- # Add system message configuration if provided
- system_message = cfg.get("system_message")
if system_message:
payload["systemMessage"] = system_message
- # Add tool filtering options
- available_tools = cfg.get("available_tools")
if available_tools is not None:
payload["availableTools"] = available_tools
- excluded_tools = cfg.get("excluded_tools")
if excluded_tools:
payload["excludedTools"] = excluded_tools
- # Always enable permission request callback (deny by default if no handler provided)
- on_permission_request = cfg.get("on_permission_request")
payload["requestPermission"] = True
- # Enable user input request callback if handler provided
- on_user_input_request = cfg.get("on_user_input_request")
if on_user_input_request:
payload["requestUserInput"] = True
- # Enable hooks callback if any hook handler provided
- hooks = cfg.get("hooks")
if hooks and any(hooks.values()):
payload["hooks"] = True
- # Add working directory if provided
- working_directory = cfg.get("working_directory")
if working_directory:
payload["workingDirectory"] = working_directory
- # Add streaming option if provided
- streaming = cfg.get("streaming")
if streaming is not None:
payload["streaming"] = streaming
- # Add provider configuration if provided
- provider = cfg.get("provider")
if provider:
payload["provider"] = self._convert_provider_to_wire_format(provider)
- # Add MCP servers configuration if provided
- mcp_servers = cfg.get("mcp_servers")
if mcp_servers:
payload["mcpServers"] = mcp_servers
payload["envValueMode"] = "direct"
- # Add custom agents configuration if provided
- custom_agents = cfg.get("custom_agents")
if custom_agents:
payload["customAgents"] = [
self._convert_custom_agent_to_wire_format(agent) for agent in custom_agents
]
- # Add config directory override if provided
- config_dir = cfg.get("config_dir")
if config_dir:
payload["configDir"] = config_dir
- # Add skill directories configuration if provided
- skill_directories = cfg.get("skill_directories")
if skill_directories:
payload["skillDirectories"] = skill_directories
- # Add disabled skills configuration if provided
- disabled_skills = cfg.get("disabled_skills")
if disabled_skills:
payload["disabledSkills"] = disabled_skills
- # Add infinite sessions configuration if provided
- infinite_sessions = cfg.get("infinite_sessions")
if infinite_sessions:
wire_config: dict[str, Any] = {}
if "enabled" in infinite_sessions:
diff --git a/python/copilot/types.py b/python/copilot/types.py
index 142aee474..959f8319c 100644
--- a/python/copilot/types.py
+++ b/python/copilot/types.py
@@ -463,55 +463,6 @@ class InfiniteSessionConfig(TypedDict, total=False):
buffer_exhaustion_threshold: float
-# Configuration for creating a session
-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 (takes precedence over excluded_tools)
- available_tools: list[str]
- # List of tool names to disable (ignored if available_tools is set)
- excluded_tools: list[str]
- # Handler for permission requests from the server
- on_permission_request: _PermissionHandlerFn
- # 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
- # 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
- # MCP server configurations for the session
- mcp_servers: dict[str, MCPServerConfig]
- # Custom agent configurations for the session
- custom_agents: list[CustomAgentConfig]
- # 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]
- # 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
-
-
# Azure-specific provider options
class AzureProviderOptions(TypedDict, total=False):
"""Azure-specific provider configuration"""
diff --git a/python/e2e/test_agent_and_compact_rpc.py b/python/e2e/test_agent_and_compact_rpc.py
index a960c8426..58b3972c7 100644
--- a/python/e2e/test_agent_and_compact_rpc.py
+++ b/python/e2e/test_agent_and_compact_rpc.py
@@ -19,23 +19,21 @@ async def test_should_list_available_custom_agents(self):
try:
await client.start()
session = await client.create_session(
- {
- "on_permission_request": PermissionHandler.approve_all,
- "custom_agents": [
- {
- "name": "test-agent",
- "display_name": "Test Agent",
- "description": "A test agent",
- "prompt": "You are a test agent.",
- },
- {
- "name": "another-agent",
- "display_name": "Another Agent",
- "description": "Another test agent",
- "prompt": "You are another agent.",
- },
- ],
- }
+ PermissionHandler.approve_all,
+ custom_agents=[
+ {
+ "name": "test-agent",
+ "display_name": "Test Agent",
+ "description": "A test agent",
+ "prompt": "You are a test agent.",
+ },
+ {
+ "name": "another-agent",
+ "display_name": "Another Agent",
+ "description": "Another test agent",
+ "prompt": "You are another agent.",
+ },
+ ],
)
result = await session.rpc.agent.list()
@@ -59,17 +57,15 @@ async def test_should_return_null_when_no_agent_is_selected(self):
try:
await client.start()
session = await client.create_session(
- {
- "on_permission_request": PermissionHandler.approve_all,
- "custom_agents": [
- {
- "name": "test-agent",
- "display_name": "Test Agent",
- "description": "A test agent",
- "prompt": "You are a test agent.",
- }
- ],
- }
+ PermissionHandler.approve_all,
+ custom_agents=[
+ {
+ "name": "test-agent",
+ "display_name": "Test Agent",
+ "description": "A test agent",
+ "prompt": "You are a test agent.",
+ }
+ ],
)
result = await session.rpc.agent.get_current()
@@ -88,17 +84,15 @@ async def test_should_select_and_get_current_agent(self):
try:
await client.start()
session = await client.create_session(
- {
- "on_permission_request": PermissionHandler.approve_all,
- "custom_agents": [
- {
- "name": "test-agent",
- "display_name": "Test Agent",
- "description": "A test agent",
- "prompt": "You are a test agent.",
- }
- ],
- }
+ PermissionHandler.approve_all,
+ custom_agents=[
+ {
+ "name": "test-agent",
+ "display_name": "Test Agent",
+ "description": "A test agent",
+ "prompt": "You are a test agent.",
+ }
+ ],
)
# Select the agent
@@ -127,17 +121,15 @@ async def test_should_deselect_current_agent(self):
try:
await client.start()
session = await client.create_session(
- {
- "on_permission_request": PermissionHandler.approve_all,
- "custom_agents": [
- {
- "name": "test-agent",
- "display_name": "Test Agent",
- "description": "A test agent",
- "prompt": "You are a test agent.",
- }
- ],
- }
+ PermissionHandler.approve_all,
+ custom_agents=[
+ {
+ "name": "test-agent",
+ "display_name": "Test Agent",
+ "description": "A test agent",
+ "prompt": "You are a test agent.",
+ }
+ ],
)
# Select then deselect
@@ -160,9 +152,7 @@ async def test_should_return_empty_list_when_no_custom_agents_configured(self):
try:
await client.start()
- session = await client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session = await client.create_session(PermissionHandler.approve_all)
result = await session.rpc.agent.list()
assert result.agents == []
@@ -177,9 +167,7 @@ class TestSessionCompactionRpc:
@pytest.mark.asyncio
async def test_should_compact_session_history_after_messages(self, ctx: E2ETestContext):
"""Test compacting session history via RPC."""
- session = await ctx.client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session = await ctx.client.create_session(PermissionHandler.approve_all)
# Send a message to create some history
await session.send_and_wait({"prompt": "What is 2+2?"})
diff --git a/python/e2e/test_ask_user.py b/python/e2e/test_ask_user.py
index f409e460c..9942e4e20 100644
--- a/python/e2e/test_ask_user.py
+++ b/python/e2e/test_ask_user.py
@@ -30,10 +30,8 @@ async def on_user_input_request(request, invocation):
}
session = await ctx.client.create_session(
- {
- "on_user_input_request": on_user_input_request,
- "on_permission_request": PermissionHandler.approve_all,
- }
+ PermissionHandler.approve_all,
+ on_user_input_request=on_user_input_request,
)
await session.send_and_wait(
@@ -69,10 +67,8 @@ async def on_user_input_request(request, invocation):
}
session = await ctx.client.create_session(
- {
- "on_user_input_request": on_user_input_request,
- "on_permission_request": PermissionHandler.approve_all,
- }
+ PermissionHandler.approve_all,
+ on_user_input_request=on_user_input_request,
)
await session.send_and_wait(
@@ -110,10 +106,8 @@ async def on_user_input_request(request, invocation):
}
session = await ctx.client.create_session(
- {
- "on_user_input_request": on_user_input_request,
- "on_permission_request": PermissionHandler.approve_all,
- }
+ PermissionHandler.approve_all,
+ on_user_input_request=on_user_input_request,
)
response = await session.send_and_wait(
diff --git a/python/e2e/test_client.py b/python/e2e/test_client.py
index cc5d31ac6..59386335c 100644
--- a/python/e2e/test_client.py
+++ b/python/e2e/test_client.py
@@ -51,7 +51,7 @@ async def test_should_return_errors_on_failed_cleanup(self):
client = CopilotClient({"cli_path": CLI_PATH})
try:
- await client.create_session({"on_permission_request": PermissionHandler.approve_all})
+ await client.create_session(PermissionHandler.approve_all)
# Kill the server process to force cleanup to fail
process = client._process
@@ -69,7 +69,7 @@ async def test_should_return_errors_on_failed_cleanup(self):
async def test_should_force_stop_without_cleanup(self):
client = CopilotClient({"cli_path": CLI_PATH})
- await client.create_session({"on_permission_request": PermissionHandler.approve_all})
+ await client.create_session(PermissionHandler.approve_all)
await client.force_stop()
assert client.get_state() == "disconnected"
@@ -206,9 +206,7 @@ async def test_should_report_error_with_stderr_when_cli_fails_to_start(self):
# Verify subsequent calls also fail (don't hang)
with pytest.raises(Exception) as exc_info2:
- session = await client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session = await client.create_session(PermissionHandler.approve_all)
await session.send("test")
# Error message varies by platform (EINVAL on Windows, EPIPE on Linux)
error_msg = str(exc_info2.value).lower()
diff --git a/python/e2e/test_compaction.py b/python/e2e/test_compaction.py
index 5447b4bad..6df20bf02 100644
--- a/python/e2e/test_compaction.py
+++ b/python/e2e/test_compaction.py
@@ -17,16 +17,14 @@ async def test_should_trigger_compaction_with_low_threshold_and_emit_events(
):
# Create session with very low compaction thresholds to trigger compaction quickly
session = await ctx.client.create_session(
- {
- "infinite_sessions": {
- "enabled": True,
- # Trigger background compaction at 0.5% context usage (~1000 tokens)
- "background_compaction_threshold": 0.005,
- # Block at 1% to ensure compaction runs
- "buffer_exhaustion_threshold": 0.01,
- },
- "on_permission_request": PermissionHandler.approve_all,
- }
+ PermissionHandler.approve_all,
+ infinite_sessions={
+ "enabled": True,
+ # Trigger background compaction at 0.5% context usage (~1000 tokens)
+ "background_compaction_threshold": 0.005,
+ # Block at 1% to ensure compaction runs
+ "buffer_exhaustion_threshold": 0.01,
+ },
)
compaction_start_events = []
@@ -72,10 +70,8 @@ async def test_should_not_emit_compaction_events_when_infinite_sessions_disabled
self, ctx: E2ETestContext
):
session = await ctx.client.create_session(
- {
- "infinite_sessions": {"enabled": False},
- "on_permission_request": PermissionHandler.approve_all,
- }
+ PermissionHandler.approve_all,
+ infinite_sessions={"enabled": False},
)
compaction_events = []
diff --git a/python/e2e/test_hooks.py b/python/e2e/test_hooks.py
index 8278fb33c..a4d432b10 100644
--- a/python/e2e/test_hooks.py
+++ b/python/e2e/test_hooks.py
@@ -24,10 +24,8 @@ async def on_pre_tool_use(input_data, invocation):
return {"permissionDecision": "allow"}
session = await ctx.client.create_session(
- {
- "hooks": {"on_pre_tool_use": on_pre_tool_use},
- "on_permission_request": PermissionHandler.approve_all,
- }
+ PermissionHandler.approve_all,
+ hooks={"on_pre_tool_use": on_pre_tool_use},
)
# Create a file for the model to read
@@ -57,10 +55,8 @@ async def on_post_tool_use(input_data, invocation):
return None
session = await ctx.client.create_session(
- {
- "hooks": {"on_post_tool_use": on_post_tool_use},
- "on_permission_request": PermissionHandler.approve_all,
- }
+ PermissionHandler.approve_all,
+ hooks={"on_post_tool_use": on_post_tool_use},
)
# Create a file for the model to read
@@ -95,13 +91,11 @@ async def on_post_tool_use(input_data, invocation):
return None
session = await ctx.client.create_session(
- {
- "hooks": {
- "on_pre_tool_use": on_pre_tool_use,
- "on_post_tool_use": on_post_tool_use,
- },
- "on_permission_request": PermissionHandler.approve_all,
- }
+ PermissionHandler.approve_all,
+ hooks={
+ "on_pre_tool_use": on_pre_tool_use,
+ "on_post_tool_use": on_post_tool_use,
+ },
)
write_file(ctx.work_dir, "both.txt", "Testing both hooks!")
@@ -132,10 +126,8 @@ async def on_pre_tool_use(input_data, invocation):
return {"permissionDecision": "deny"}
session = await ctx.client.create_session(
- {
- "hooks": {"on_pre_tool_use": on_pre_tool_use},
- "on_permission_request": PermissionHandler.approve_all,
- }
+ PermissionHandler.approve_all,
+ hooks={"on_pre_tool_use": on_pre_tool_use},
)
# Create a file
diff --git a/python/e2e/test_mcp_and_agents.py b/python/e2e/test_mcp_and_agents.py
index b29a54827..c8659f1c1 100644
--- a/python/e2e/test_mcp_and_agents.py
+++ b/python/e2e/test_mcp_and_agents.py
@@ -33,7 +33,7 @@ async def test_should_accept_mcp_server_configuration_on_session_create(
}
session = await ctx.client.create_session(
- {"mcp_servers": mcp_servers, "on_permission_request": PermissionHandler.approve_all}
+ PermissionHandler.approve_all, mcp_servers=mcp_servers
)
assert session.session_id is not None
@@ -50,9 +50,7 @@ async def test_should_accept_mcp_server_configuration_on_session_resume(
):
"""Test that MCP server configuration is accepted on session resume"""
# Create a session first
- session1 = await ctx.client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session1 = await ctx.client.create_session(PermissionHandler.approve_all)
session_id = session1.session_id
await session1.send_and_wait({"prompt": "What is 1+1?"})
@@ -95,10 +93,7 @@ async def test_should_pass_literal_env_values_to_mcp_server_subprocess(
}
session = await ctx.client.create_session(
- {
- "mcp_servers": mcp_servers,
- "on_permission_request": PermissionHandler.approve_all,
- }
+ PermissionHandler.approve_all, mcp_servers=mcp_servers
)
assert session.session_id is not None
@@ -131,7 +126,7 @@ async def test_should_accept_custom_agent_configuration_on_session_create(
]
session = await ctx.client.create_session(
- {"custom_agents": custom_agents, "on_permission_request": PermissionHandler.approve_all}
+ PermissionHandler.approve_all, custom_agents=custom_agents
)
assert session.session_id is not None
@@ -148,9 +143,7 @@ async def test_should_accept_custom_agent_configuration_on_session_resume(
):
"""Test that custom agent configuration is accepted on session resume"""
# Create a session first
- session1 = await ctx.client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session1 = await ctx.client.create_session(PermissionHandler.approve_all)
session_id = session1.session_id
await session1.send_and_wait({"prompt": "What is 1+1?"})
@@ -203,11 +196,9 @@ async def test_should_accept_both_mcp_servers_and_custom_agents(self, ctx: E2ETe
]
session = await ctx.client.create_session(
- {
- "mcp_servers": mcp_servers,
- "custom_agents": custom_agents,
- "on_permission_request": PermissionHandler.approve_all,
- }
+ PermissionHandler.approve_all,
+ mcp_servers=mcp_servers,
+ custom_agents=custom_agents,
)
assert session.session_id is not None
diff --git a/python/e2e/test_permissions.py b/python/e2e/test_permissions.py
index c116053ba..a4242f46c 100644
--- a/python/e2e/test_permissions.py
+++ b/python/e2e/test_permissions.py
@@ -27,7 +27,7 @@ def on_permission_request(
# Approve the permission
return {"kind": "approved"}
- session = await ctx.client.create_session({"on_permission_request": on_permission_request})
+ session = await ctx.client.create_session(on_permission_request)
write_file(ctx.work_dir, "test.txt", "original content")
@@ -53,7 +53,7 @@ def on_permission_request(
# Deny all permissions
return {"kind": "denied-interactively-by-user"}
- session = await ctx.client.create_session({"on_permission_request": on_permission_request})
+ session = await ctx.client.create_session(on_permission_request)
original_content = "protected content"
write_file(ctx.work_dir, "protected.txt", original_content)
@@ -76,7 +76,7 @@ async def test_should_deny_tool_operations_when_handler_explicitly_denies(
def deny_all(request, invocation):
return {"kind": "denied-no-approval-rule-and-could-not-request-from-user"}
- session = await ctx.client.create_session({"on_permission_request": deny_all})
+ session = await ctx.client.create_session(deny_all)
denied_events = []
done_event = asyncio.Event()
@@ -107,9 +107,7 @@ async def test_should_deny_tool_operations_when_handler_explicitly_denies_after_
self, ctx: E2ETestContext
):
"""Test that tool operations are denied after resume when handler explicitly denies"""
- session1 = await ctx.client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session1 = await ctx.client.create_session(PermissionHandler.approve_all)
session_id = session1.session_id
await session1.send_and_wait({"prompt": "What is 1+1?"})
@@ -145,9 +143,7 @@ def on_event(event):
async def test_should_work_with_approve_all_permission_handler(self, ctx: E2ETestContext):
"""Test that sessions work with approve-all permission handler"""
- session = await ctx.client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session = await ctx.client.create_session(PermissionHandler.approve_all)
message = await session.send_and_wait({"prompt": "What is 2+2?"})
@@ -168,7 +164,7 @@ async def on_permission_request(
await asyncio.sleep(0.01)
return {"kind": "approved"}
- session = await ctx.client.create_session({"on_permission_request": on_permission_request})
+ session = await ctx.client.create_session(on_permission_request)
await session.send_and_wait({"prompt": "Run 'echo test' and tell me what happens"})
@@ -181,9 +177,7 @@ async def test_should_resume_session_with_permission_handler(self, ctx: E2ETestC
permission_requests = []
# Create initial session
- session1 = await ctx.client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session1 = await ctx.client.create_session(PermissionHandler.approve_all)
session_id = session1.session_id
await session1.send_and_wait({"prompt": "What is 1+1?"})
@@ -213,7 +207,7 @@ def on_permission_request(
) -> PermissionRequestResult:
raise RuntimeError("Handler error")
- session = await ctx.client.create_session({"on_permission_request": on_permission_request})
+ session = await ctx.client.create_session(on_permission_request)
message = await session.send_and_wait(
{"prompt": "Run 'echo test'. If you can't, say 'failed'."}
@@ -240,7 +234,7 @@ def on_permission_request(
assert len(request["toolCallId"]) > 0
return {"kind": "approved"}
- session = await ctx.client.create_session({"on_permission_request": on_permission_request})
+ session = await ctx.client.create_session(on_permission_request)
await session.send_and_wait({"prompt": "Run 'echo test'"})
diff --git a/python/e2e/test_rpc.py b/python/e2e/test_rpc.py
index 240cd3730..96101759c 100644
--- a/python/e2e/test_rpc.py
+++ b/python/e2e/test_rpc.py
@@ -78,7 +78,7 @@ class TestSessionRpc:
async def test_should_call_session_rpc_model_get_current(self, ctx: E2ETestContext):
"""Test calling session.rpc.model.getCurrent"""
session = await ctx.client.create_session(
- {"model": "claude-sonnet-4.5", "on_permission_request": PermissionHandler.approve_all}
+ PermissionHandler.approve_all, "claude-sonnet-4.5"
)
result = await session.rpc.model.get_current()
@@ -92,7 +92,7 @@ async def test_should_call_session_rpc_model_switch_to(self, ctx: E2ETestContext
from copilot.generated.rpc import SessionModelSwitchToParams
session = await ctx.client.create_session(
- {"model": "claude-sonnet-4.5", "on_permission_request": PermissionHandler.approve_all}
+ PermissionHandler.approve_all, "claude-sonnet-4.5"
)
# Get initial model
@@ -116,9 +116,7 @@ async def test_get_and_set_session_mode(self):
try:
await client.start()
- session = await client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session = await client.create_session(PermissionHandler.approve_all)
# Get initial mode (default should be interactive)
initial = await session.rpc.mode.get()
@@ -152,9 +150,7 @@ async def test_read_update_and_delete_plan(self):
try:
await client.start()
- session = await client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session = await client.create_session(PermissionHandler.approve_all)
# Initially plan should not exist
initial = await session.rpc.plan.read()
@@ -195,9 +191,7 @@ async def test_create_list_and_read_workspace_files(self):
try:
await client.start()
- session = await client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session = await client.create_session(PermissionHandler.approve_all)
# Initially no files
initial_files = await session.rpc.workspace.list_files()
diff --git a/python/e2e/test_session.py b/python/e2e/test_session.py
index 4842d7829..438a5abca 100644
--- a/python/e2e/test_session.py
+++ b/python/e2e/test_session.py
@@ -14,9 +14,7 @@
class TestSessions:
async def test_should_create_and_destroy_sessions(self, ctx: E2ETestContext):
- session = await ctx.client.create_session(
- {"model": "fake-test-model", "on_permission_request": PermissionHandler.approve_all}
- )
+ session = await ctx.client.create_session(PermissionHandler.approve_all, "fake-test-model")
assert session.session_id
messages = await session.get_messages()
@@ -31,9 +29,7 @@ async def test_should_create_and_destroy_sessions(self, ctx: E2ETestContext):
await session.get_messages()
async def test_should_have_stateful_conversation(self, ctx: E2ETestContext):
- session = await ctx.client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session = await ctx.client.create_session(PermissionHandler.approve_all)
assistant_message = await session.send_and_wait({"prompt": "What is 1+1?"})
assert assistant_message is not None
@@ -50,10 +46,8 @@ async def test_should_create_a_session_with_appended_systemMessage_config(
):
system_message_suffix = "End each response with the phrase 'Have a nice day!'"
session = await ctx.client.create_session(
- {
- "system_message": {"mode": "append", "content": system_message_suffix},
- "on_permission_request": PermissionHandler.approve_all,
- }
+ PermissionHandler.approve_all,
+ system_message={"mode": "append", "content": system_message_suffix},
)
await session.send({"prompt": "What is your full name?"})
@@ -72,10 +66,8 @@ async def test_should_create_a_session_with_replaced_systemMessage_config(
):
test_system_message = "You are an assistant called Testy McTestface. Reply succinctly."
session = await ctx.client.create_session(
- {
- "system_message": {"mode": "replace", "content": test_system_message},
- "on_permission_request": PermissionHandler.approve_all,
- }
+ PermissionHandler.approve_all,
+ system_message={"mode": "replace", "content": test_system_message},
)
await session.send({"prompt": "What is your full name?"})
@@ -90,10 +82,8 @@ async def test_should_create_a_session_with_replaced_systemMessage_config(
async def test_should_create_a_session_with_availableTools(self, ctx: E2ETestContext):
session = await ctx.client.create_session(
- {
- "available_tools": ["view", "edit"],
- "on_permission_request": PermissionHandler.approve_all,
- }
+ PermissionHandler.approve_all,
+ available_tools=["view", "edit"],
)
await session.send({"prompt": "What is 1+1?"})
@@ -109,7 +99,7 @@ async def test_should_create_a_session_with_availableTools(self, ctx: E2ETestCon
async def test_should_create_a_session_with_excludedTools(self, ctx: E2ETestContext):
session = await ctx.client.create_session(
- {"excluded_tools": ["view"], "on_permission_request": PermissionHandler.approve_all}
+ PermissionHandler.approve_all, excluded_tools=["view"]
)
await session.send({"prompt": "What is 1+1?"})
@@ -132,9 +122,9 @@ async def test_should_handle_multiple_concurrent_sessions(self, ctx: E2ETestCont
import asyncio
s1, s2, s3 = await asyncio.gather(
- ctx.client.create_session({"on_permission_request": PermissionHandler.approve_all}),
- ctx.client.create_session({"on_permission_request": PermissionHandler.approve_all}),
- ctx.client.create_session({"on_permission_request": PermissionHandler.approve_all}),
+ ctx.client.create_session(PermissionHandler.approve_all),
+ ctx.client.create_session(PermissionHandler.approve_all),
+ ctx.client.create_session(PermissionHandler.approve_all),
)
# All sessions should have unique IDs
@@ -156,9 +146,7 @@ async def test_should_handle_multiple_concurrent_sessions(self, ctx: E2ETestCont
async def test_should_resume_a_session_using_the_same_client(self, ctx: E2ETestContext):
# Create initial session
- session1 = await ctx.client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session1 = await ctx.client.create_session(PermissionHandler.approve_all)
session_id = session1.session_id
answer = await session1.send_and_wait({"prompt": "What is 1+1?"})
assert answer is not None
@@ -174,9 +162,7 @@ async def test_should_resume_a_session_using_the_same_client(self, ctx: E2ETestC
async def test_should_resume_a_session_using_a_new_client(self, ctx: E2ETestContext):
# Create initial session
- session1 = await ctx.client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session1 = await ctx.client.create_session(PermissionHandler.approve_all)
session_id = session1.session_id
answer = await session1.send_and_wait({"prompt": "What is 1+1?"})
assert answer is not None
@@ -219,13 +205,9 @@ async def test_should_list_sessions(self, ctx: E2ETestContext):
import asyncio
# Create a couple of sessions and send messages to persist them
- session1 = await ctx.client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session1 = await ctx.client.create_session(PermissionHandler.approve_all)
await session1.send_and_wait({"prompt": "Say hello"})
- session2 = await ctx.client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session2 = await ctx.client.create_session(PermissionHandler.approve_all)
await session2.send_and_wait({"prompt": "Say goodbye"})
# Small delay to ensure session files are written to disk
@@ -262,9 +244,7 @@ async def test_should_delete_session(self, ctx: E2ETestContext):
import asyncio
# Create a session and send a message to persist it
- session = await ctx.client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session = await ctx.client.create_session(PermissionHandler.approve_all)
await session.send_and_wait({"prompt": "Hello"})
session_id = session.session_id
@@ -300,21 +280,19 @@ def get_secret_number_handler(invocation):
}
session = await ctx.client.create_session(
- {
- "tools": [
- Tool(
- name="get_secret_number",
- description="Gets the secret number",
- handler=get_secret_number_handler,
- parameters={
- "type": "object",
- "properties": {"key": {"type": "string", "description": "Key"}},
- "required": ["key"],
- },
- )
- ],
- "on_permission_request": PermissionHandler.approve_all,
- }
+ PermissionHandler.approve_all,
+ tools=[
+ Tool(
+ name="get_secret_number",
+ description="Gets the secret number",
+ handler=get_secret_number_handler,
+ parameters={
+ "type": "object",
+ "properties": {"key": {"type": "string", "description": "Key"}},
+ "required": ["key"],
+ },
+ )
+ ],
)
answer = await session.send_and_wait({"prompt": "What is the secret number for key ALPHA?"})
@@ -323,37 +301,31 @@ def get_secret_number_handler(invocation):
async def test_should_create_session_with_custom_provider(self, ctx: E2ETestContext):
session = await ctx.client.create_session(
- {
- "provider": {
- "type": "openai",
- "base_url": "https://api.openai.com/v1",
- "api_key": "fake-key",
- },
- "on_permission_request": PermissionHandler.approve_all,
- }
+ PermissionHandler.approve_all,
+ provider={
+ "type": "openai",
+ "base_url": "https://api.openai.com/v1",
+ "api_key": "fake-key",
+ },
)
assert session.session_id
async def test_should_create_session_with_azure_provider(self, ctx: E2ETestContext):
session = await ctx.client.create_session(
- {
- "provider": {
- "type": "azure",
- "base_url": "https://my-resource.openai.azure.com",
- "api_key": "fake-key",
- "azure": {
- "api_version": "2024-02-15-preview",
- },
+ PermissionHandler.approve_all,
+ provider={
+ "type": "azure",
+ "base_url": "https://my-resource.openai.azure.com",
+ "api_key": "fake-key",
+ "azure": {
+ "api_version": "2024-02-15-preview",
},
- "on_permission_request": PermissionHandler.approve_all,
- }
+ },
)
assert session.session_id
async def test_should_resume_session_with_custom_provider(self, ctx: E2ETestContext):
- session = await ctx.client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session = await ctx.client.create_session(PermissionHandler.approve_all)
session_id = session.session_id
# Resume the session with a provider
@@ -374,9 +346,7 @@ async def test_should_resume_session_with_custom_provider(self, ctx: E2ETestCont
async def test_should_abort_a_session(self, ctx: E2ETestContext):
import asyncio
- session = await ctx.client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session = await ctx.client.create_session(PermissionHandler.approve_all)
# Set up event listeners BEFORE sending to avoid race conditions
wait_for_tool_start = asyncio.create_task(
@@ -422,9 +392,7 @@ async def test_should_receive_streaming_delta_events_when_streaming_is_enabled(
):
import asyncio
- session = await ctx.client.create_session(
- {"streaming": True, "on_permission_request": PermissionHandler.approve_all}
- )
+ session = await ctx.client.create_session(PermissionHandler.approve_all, streaming=True)
delta_contents = []
done_event = asyncio.Event()
@@ -465,9 +433,7 @@ def on_event(event):
async def test_should_pass_streaming_option_to_session_creation(self, ctx: E2ETestContext):
# Verify that the streaming option is accepted without errors
- session = await ctx.client.create_session(
- {"streaming": True, "on_permission_request": PermissionHandler.approve_all}
- )
+ session = await ctx.client.create_session(PermissionHandler.approve_all, streaming=True)
assert session.session_id
@@ -479,9 +445,7 @@ async def test_should_pass_streaming_option_to_session_creation(self, ctx: E2ETe
async def test_should_receive_session_events(self, ctx: E2ETestContext):
import asyncio
- session = await ctx.client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session = await ctx.client.create_session(PermissionHandler.approve_all)
received_events = []
idle_event = asyncio.Event()
@@ -517,10 +481,7 @@ async def test_should_create_session_with_custom_config_dir(self, ctx: E2ETestCo
custom_config_dir = os.path.join(ctx.home_dir, "custom-config")
session = await ctx.client.create_session(
- {
- "config_dir": custom_config_dir,
- "on_permission_request": PermissionHandler.approve_all,
- }
+ PermissionHandler.approve_all, config_dir=custom_config_dir
)
assert session.session_id
diff --git a/python/e2e/test_skills.py b/python/e2e/test_skills.py
index 10d32695c..3eaa1cda4 100644
--- a/python/e2e/test_skills.py
+++ b/python/e2e/test_skills.py
@@ -56,10 +56,7 @@ async def test_should_load_and_apply_skill_from_skilldirectories(self, ctx: E2ET
"""Test that skills are loaded and applied from skillDirectories"""
skills_dir = create_skill_dir(ctx.work_dir)
session = await ctx.client.create_session(
- {
- "skill_directories": [skills_dir],
- "on_permission_request": PermissionHandler.approve_all,
- }
+ PermissionHandler.approve_all, skill_directories=[skills_dir]
)
assert session.session_id is not None
@@ -77,11 +74,9 @@ async def test_should_not_apply_skill_when_disabled_via_disabledskills(
"""Test that disabledSkills prevents skill from being applied"""
skills_dir = create_skill_dir(ctx.work_dir)
session = await ctx.client.create_session(
- {
- "skill_directories": [skills_dir],
- "disabled_skills": ["test-skill"],
- "on_permission_request": PermissionHandler.approve_all,
- }
+ PermissionHandler.approve_all,
+ skill_directories=[skills_dir],
+ disabled_skills=["test-skill"],
)
assert session.session_id is not None
@@ -104,9 +99,7 @@ async def test_should_apply_skill_on_session_resume_with_skilldirectories(
skills_dir = create_skill_dir(ctx.work_dir)
# Create a session without skills first
- session1 = await ctx.client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session1 = await ctx.client.create_session(PermissionHandler.approve_all)
session_id = session1.session_id
# First message without skill - marker should not appear
diff --git a/python/e2e/test_tools.py b/python/e2e/test_tools.py
index e4a9f5f06..81b0533f3 100644
--- a/python/e2e/test_tools.py
+++ b/python/e2e/test_tools.py
@@ -18,9 +18,7 @@ async def test_invokes_built_in_tools(self, ctx: E2ETestContext):
with open(readme_path, "w") as f:
f.write("# ELIZA, the only chatbot you'll ever need")
- session = await ctx.client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session = await ctx.client.create_session(PermissionHandler.approve_all)
await session.send({"prompt": "What's the first line of README.md in this directory?"})
assistant_message = await get_final_assistant_message(session)
@@ -35,7 +33,7 @@ def encrypt_string(params: EncryptParams, invocation: ToolInvocation) -> str:
return params.input.upper()
session = await ctx.client.create_session(
- {"tools": [encrypt_string], "on_permission_request": PermissionHandler.approve_all}
+ PermissionHandler.approve_all, tools=[encrypt_string]
)
await session.send({"prompt": "Use encrypt_string to encrypt this string: Hello"})
@@ -48,7 +46,7 @@ def get_user_location() -> str:
raise Exception("Melbourne")
session = await ctx.client.create_session(
- {"tools": [get_user_location], "on_permission_request": PermissionHandler.approve_all}
+ PermissionHandler.approve_all, tools=[get_user_location]
)
await session.send(
@@ -112,9 +110,7 @@ def db_query(params: DbQueryParams, invocation: ToolInvocation) -> list[City]:
City(countryId=12, cityName="San Lorenzo", population=204356),
]
- session = await ctx.client.create_session(
- {"tools": [db_query], "on_permission_request": PermissionHandler.approve_all}
- )
+ session = await ctx.client.create_session(PermissionHandler.approve_all, tools=[db_query])
expected_session_id = session.session_id
await session.send(
@@ -147,12 +143,7 @@ def on_permission_request(request, invocation):
permission_requests.append(request)
return {"kind": "approved"}
- session = await ctx.client.create_session(
- {
- "tools": [encrypt_string],
- "on_permission_request": on_permission_request,
- }
- )
+ session = await ctx.client.create_session(on_permission_request, tools=[encrypt_string])
await session.send({"prompt": "Use encrypt_string to encrypt this string: Hello"})
assistant_message = await get_final_assistant_message(session)
@@ -178,12 +169,7 @@ def encrypt_string(params: EncryptParams, invocation: ToolInvocation) -> str:
def on_permission_request(request, invocation):
return {"kind": "denied-interactively-by-user"}
- session = await ctx.client.create_session(
- {
- "tools": [encrypt_string],
- "on_permission_request": on_permission_request,
- }
- )
+ session = await ctx.client.create_session(on_permission_request, tools=[encrypt_string])
await session.send({"prompt": "Use encrypt_string to encrypt this string: Hello"})
await get_final_assistant_message(session)
diff --git a/python/samples/chat.py b/python/samples/chat.py
index eb781e4e2..c04b148dd 100644
--- a/python/samples/chat.py
+++ b/python/samples/chat.py
@@ -9,11 +9,7 @@
async def main():
client = CopilotClient()
await client.start()
- session = await client.create_session(
- {
- "on_permission_request": PermissionHandler.approve_all,
- }
- )
+ session = await client.create_session(PermissionHandler.approve_all)
def on_event(event):
output = None
diff --git a/python/test_client.py b/python/test_client.py
index c6ad027f5..a7e61ca03 100644
--- a/python/test_client.py
+++ b/python/test_client.py
@@ -16,8 +16,18 @@ async def test_create_session_raises_without_permission_handler(self):
client = CopilotClient({"cli_path": CLI_PATH})
await client.start()
try:
- with pytest.raises(ValueError, match="on_permission_request.*is required"):
- await client.create_session({})
+ with pytest.raises(TypeError, match="on_permission_request"):
+ await client.create_session() # type: ignore[call-arg]
+ finally:
+ await client.force_stop()
+
+ @pytest.mark.asyncio
+ async def test_create_session_raises_with_none_permission_handler(self):
+ client = CopilotClient({"cli_path": CLI_PATH})
+ await client.start()
+ try:
+ with pytest.raises(ValueError, match="on_permission_request handler is required"):
+ await client.create_session(None) # type: ignore[arg-type]
finally:
await client.force_stop()
@@ -26,9 +36,7 @@ async def test_resume_session_raises_without_permission_handler(self):
client = CopilotClient({"cli_path": CLI_PATH})
await client.start()
try:
- session = await client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session = await client.create_session(PermissionHandler.approve_all)
with pytest.raises(ValueError, match="on_permission_request.*is required"):
await client.resume_session(session.session_id, {})
finally:
@@ -42,9 +50,7 @@ async def test_returns_failure_when_tool_not_registered(self):
await client.start()
try:
- session = await client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session = await client.create_session(PermissionHandler.approve_all)
response = await client._handle_tool_call_request(
{
@@ -191,9 +197,7 @@ async def mock_request(method, params):
return await original_request(method, params)
client._client.request = mock_request
- await client.create_session(
- {"client_name": "my-app", "on_permission_request": PermissionHandler.approve_all}
- )
+ await client.create_session(PermissionHandler.approve_all, client_name="my-app")
assert captured["session.create"]["clientName"] == "my-app"
finally:
await client.force_stop()
@@ -204,9 +208,7 @@ async def test_resume_session_forwards_client_name(self):
await client.start()
try:
- session = await client.create_session(
- {"on_permission_request": PermissionHandler.approve_all}
- )
+ session = await client.create_session(PermissionHandler.approve_all)
captured = {}
original_request = client._client.request
diff --git a/test/scenarios/auth/byok-anthropic/python/main.py b/test/scenarios/auth/byok-anthropic/python/main.py
index 7f5e5834c..34e3be00a 100644
--- a/test/scenarios/auth/byok-anthropic/python/main.py
+++ b/test/scenarios/auth/byok-anthropic/python/main.py
@@ -1,7 +1,7 @@
import asyncio
import os
import sys
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY")
ANTHROPIC_MODEL = os.environ.get("ANTHROPIC_MODEL", "claude-sonnet-4-20250514")
@@ -19,19 +19,20 @@ async def main():
client = CopilotClient(opts)
try:
- session = await client.create_session({
- "model": ANTHROPIC_MODEL,
- "provider": {
+ session = await client.create_session(
+ PermissionHandler.approve_all,
+ 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(
{"prompt": "What is the capital of France?"}
diff --git a/test/scenarios/auth/byok-azure/python/main.py b/test/scenarios/auth/byok-azure/python/main.py
index 5376cac28..bcf8f74c4 100644
--- a/test/scenarios/auth/byok-azure/python/main.py
+++ b/test/scenarios/auth/byok-azure/python/main.py
@@ -1,7 +1,7 @@
import asyncio
import os
import sys
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
AZURE_OPENAI_ENDPOINT = os.environ.get("AZURE_OPENAI_ENDPOINT")
AZURE_OPENAI_API_KEY = os.environ.get("AZURE_OPENAI_API_KEY")
@@ -20,9 +20,10 @@ async def main():
client = CopilotClient(opts)
try:
- session = await client.create_session({
- "model": AZURE_OPENAI_MODEL,
- "provider": {
+ session = await client.create_session(
+ PermissionHandler.approve_all,
+ AZURE_OPENAI_MODEL,
+ provider={
"type": "azure",
"base_url": AZURE_OPENAI_ENDPOINT,
"api_key": AZURE_OPENAI_API_KEY,
@@ -30,12 +31,12 @@ 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(
{"prompt": "What is the capital of France?"}
diff --git a/test/scenarios/auth/byok-ollama/python/main.py b/test/scenarios/auth/byok-ollama/python/main.py
index 0f9df7f54..8b1d33131 100644
--- a/test/scenarios/auth/byok-ollama/python/main.py
+++ b/test/scenarios/auth/byok-ollama/python/main.py
@@ -1,7 +1,7 @@
import asyncio
import os
import sys
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "http://localhost:11434/v1")
OLLAMA_MODEL = os.environ.get("OLLAMA_MODEL", "llama3.2:3b")
@@ -18,18 +18,19 @@ async def main():
client = CopilotClient(opts)
try:
- session = await client.create_session({
- "model": OLLAMA_MODEL,
- "provider": {
+ session = await client.create_session(
+ PermissionHandler.approve_all,
+ 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(
{"prompt": "What is the capital of France?"}
diff --git a/test/scenarios/auth/byok-openai/python/main.py b/test/scenarios/auth/byok-openai/python/main.py
index 651a92cd6..961257689 100644
--- a/test/scenarios/auth/byok-openai/python/main.py
+++ b/test/scenarios/auth/byok-openai/python/main.py
@@ -1,7 +1,7 @@
import asyncio
import os
import sys
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
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")
@@ -19,14 +19,15 @@ async def main():
client = CopilotClient(opts)
try:
- session = await client.create_session({
- "model": OPENAI_MODEL,
- "provider": {
+ session = await client.create_session(
+ PermissionHandler.approve_all,
+ OPENAI_MODEL,
+ provider={
"type": "openai",
"base_url": OPENAI_BASE_URL,
"api_key": OPENAI_API_KEY,
},
- })
+ )
response = await session.send_and_wait(
{"prompt": "What is the capital of France?"}
diff --git a/test/scenarios/auth/gh-app/python/main.py b/test/scenarios/auth/gh-app/python/main.py
index 4568c82b2..ee47d53a0 100644
--- a/test/scenarios/auth/gh-app/python/main.py
+++ b/test/scenarios/auth/gh-app/python/main.py
@@ -4,7 +4,7 @@
import time
import urllib.request
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
DEVICE_CODE_URL = "https://github.com/login/device/code"
@@ -84,7 +84,7 @@ async def main():
client = CopilotClient(opts)
try:
- session = await client.create_session({"model": "claude-haiku-4.5"})
+ session = await client.create_session(PermissionHandler.approve_all, "claude-haiku-4.5")
response = await session.send_and_wait({"prompt": "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 218505f4a..34283a17c 100644
--- a/test/scenarios/bundling/app-backend-to-server/python/main.py
+++ b/test/scenarios/bundling/app-backend-to-server/python/main.py
@@ -5,7 +5,7 @@
import urllib.request
from flask import Flask, request, jsonify
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
app = Flask(__name__)
@@ -16,7 +16,7 @@ async def ask_copilot(prompt: str) -> str:
client = CopilotClient({"cli_url": CLI_URL})
try:
- session = await client.create_session({"model": "claude-haiku-4.5"})
+ session = await client.create_session(PermissionHandler.approve_all, "claude-haiku-4.5")
response = await session.send_and_wait({"prompt": prompt})
diff --git a/test/scenarios/bundling/app-direct-server/python/main.py b/test/scenarios/bundling/app-direct-server/python/main.py
index 05aaa9270..a47c926e9 100644
--- a/test/scenarios/bundling/app-direct-server/python/main.py
+++ b/test/scenarios/bundling/app-direct-server/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
async def main():
@@ -9,7 +9,7 @@ async def main():
})
try:
- session = await client.create_session({"model": "claude-haiku-4.5"})
+ session = await client.create_session(PermissionHandler.approve_all, "claude-haiku-4.5")
response = await session.send_and_wait(
{"prompt": "What is the capital of France?"}
diff --git a/test/scenarios/bundling/container-proxy/python/main.py b/test/scenarios/bundling/container-proxy/python/main.py
index 05aaa9270..a47c926e9 100644
--- a/test/scenarios/bundling/container-proxy/python/main.py
+++ b/test/scenarios/bundling/container-proxy/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
async def main():
@@ -9,7 +9,7 @@ async def main():
})
try:
- session = await client.create_session({"model": "claude-haiku-4.5"})
+ session = await client.create_session(PermissionHandler.approve_all, "claude-haiku-4.5")
response = await session.send_and_wait(
{"prompt": "What is the capital of France?"}
diff --git a/test/scenarios/bundling/fully-bundled/python/main.py b/test/scenarios/bundling/fully-bundled/python/main.py
index 138bb5646..cfe12901f 100644
--- a/test/scenarios/bundling/fully-bundled/python/main.py
+++ b/test/scenarios/bundling/fully-bundled/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
async def main():
@@ -10,7 +10,7 @@ async def main():
client = CopilotClient(opts)
try:
- session = await client.create_session({"model": "claude-haiku-4.5"})
+ session = await client.create_session(PermissionHandler.approve_all, "claude-haiku-4.5")
response = await session.send_and_wait(
{"prompt": "What is the capital of France?"}
diff --git a/test/scenarios/callbacks/hooks/python/main.py b/test/scenarios/callbacks/hooks/python/main.py
index a00c18af7..abbef404e 100644
--- a/test/scenarios/callbacks/hooks/python/main.py
+++ b/test/scenarios/callbacks/hooks/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
hook_log: list[str] = []
@@ -47,18 +47,16 @@ async def main():
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,
- },
- }
+ auto_approve_permission,
+ "claude-haiku-4.5",
+ 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 2da5133fa..0827c8e17 100644
--- a/test/scenarios/callbacks/permissions/python/main.py
+++ b/test/scenarios/callbacks/permissions/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
# Track which tools requested permission
permission_log: list[str] = []
@@ -23,11 +23,9 @@ async def main():
try:
session = await client.create_session(
- {
- "model": "claude-haiku-4.5",
- "on_permission_request": log_permission,
- "hooks": {"on_pre_tool_use": auto_approve_tool},
- }
+ log_permission,
+ "claude-haiku-4.5",
+ 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 fb36eda5c..cef027b20 100644
--- a/test/scenarios/callbacks/user-input/python/main.py
+++ b/test/scenarios/callbacks/user-input/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
input_log: list[str] = []
@@ -27,12 +27,10 @@ async def main():
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},
- }
+ auto_approve_permission,
+ "claude-haiku-4.5",
+ 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 0abc6b709..4f708957b 100644
--- a/test/scenarios/modes/default/python/main.py
+++ b/test/scenarios/modes/default/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
async def main():
@@ -10,9 +10,7 @@ async def main():
client = CopilotClient(opts)
try:
- session = await client.create_session({
- "model": "claude-haiku-4.5",
- })
+ session = await client.create_session(PermissionHandler.approve_all, "claude-haiku-4.5")
response = await session.send_and_wait({"prompt": "Use the grep tool to search for the word 'SDK' in README.md and show the matching lines."})
if response:
diff --git a/test/scenarios/modes/minimal/python/main.py b/test/scenarios/modes/minimal/python/main.py
index 74a98ba0e..73d2d7fc7 100644
--- a/test/scenarios/modes/minimal/python/main.py
+++ b/test/scenarios/modes/minimal/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
async def main():
@@ -10,14 +10,15 @@ async def main():
client = CopilotClient(opts)
try:
- session = await client.create_session({
- "model": "claude-haiku-4.5",
- "available_tools": [],
- "system_message": {
+ session = await client.create_session(
+ PermissionHandler.approve_all,
+ "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({"prompt": "Use the grep tool to search for 'SDK' in README.md."})
if response:
diff --git a/test/scenarios/prompts/attachments/python/main.py b/test/scenarios/prompts/attachments/python/main.py
index acf9c7af1..048eddaf6 100644
--- a/test/scenarios/prompts/attachments/python/main.py
+++ b/test/scenarios/prompts/attachments/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
SYSTEM_PROMPT = """You are a helpful assistant. Answer questions about attached files concisely."""
@@ -13,11 +13,10 @@ async def main():
try:
session = await client.create_session(
- {
- "model": "claude-haiku-4.5",
- "system_message": {"mode": "replace", "content": SYSTEM_PROMPT},
- "available_tools": [],
- }
+ PermissionHandler.approve_all,
+ "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 74444e7bf..45095ae29 100644
--- a/test/scenarios/prompts/reasoning-effort/python/main.py
+++ b/test/scenarios/prompts/reasoning-effort/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
async def main():
@@ -10,15 +10,16 @@ async def main():
client = CopilotClient(opts)
try:
- session = await client.create_session({
- "model": "claude-opus-4.6",
- "reasoning_effort": "low",
- "available_tools": [],
- "system_message": {
+ session = await client.create_session(
+ PermissionHandler.approve_all,
+ "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(
{"prompt": "What is the capital of France?"}
diff --git a/test/scenarios/prompts/system-message/python/main.py b/test/scenarios/prompts/system-message/python/main.py
index a3bfccdcf..90df1729b 100644
--- a/test/scenarios/prompts/system-message/python/main.py
+++ b/test/scenarios/prompts/system-message/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
PIRATE_PROMPT = """You are a pirate. Always respond in pirate speak. Say 'Arrr!' in every response. Use nautical terms and pirate slang throughout."""
@@ -13,11 +13,10 @@ async def main():
try:
session = await client.create_session(
- {
- "model": "claude-haiku-4.5",
- "system_message": {"mode": "replace", "content": PIRATE_PROMPT},
- "available_tools": [],
- }
+ PermissionHandler.approve_all,
+ "claude-haiku-4.5",
+ system_message={"mode": "replace", "content": PIRATE_PROMPT},
+ available_tools=[],
)
response = await session.send_and_wait(
diff --git a/test/scenarios/sessions/concurrent-sessions/python/main.py b/test/scenarios/sessions/concurrent-sessions/python/main.py
index 171a202e4..52446baf9 100644
--- a/test/scenarios/sessions/concurrent-sessions/python/main.py
+++ b/test/scenarios/sessions/concurrent-sessions/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
PIRATE_PROMPT = "You are a pirate. Always say Arrr!"
ROBOT_PROMPT = "You are a robot. Always say BEEP BOOP!"
@@ -15,18 +15,16 @@ async def main():
try:
session1, session2 = await asyncio.gather(
client.create_session(
- {
- "model": "claude-haiku-4.5",
- "system_message": {"mode": "replace", "content": PIRATE_PROMPT},
- "available_tools": [],
- }
+ PermissionHandler.approve_all,
+ "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": [],
- }
+ PermissionHandler.approve_all,
+ "claude-haiku-4.5",
+ system_message={"mode": "replace", "content": ROBOT_PROMPT},
+ available_tools=[],
),
)
diff --git a/test/scenarios/sessions/infinite-sessions/python/main.py b/test/scenarios/sessions/infinite-sessions/python/main.py
index fe39a7117..13fbb1623 100644
--- a/test/scenarios/sessions/infinite-sessions/python/main.py
+++ b/test/scenarios/sessions/infinite-sessions/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
async def main():
@@ -10,19 +10,20 @@ async def main():
client = CopilotClient(opts)
try:
- session = await client.create_session({
- "model": "claude-haiku-4.5",
- "available_tools": [],
- "system_message": {
+ session = await client.create_session(
+ PermissionHandler.approve_all,
+ "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 b65370b97..cb176b92c 100644
--- a/test/scenarios/sessions/session-resume/python/main.py
+++ b/test/scenarios/sessions/session-resume/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
async def main():
@@ -12,10 +12,9 @@ async def main():
try:
# 1. Create a session
session = await client.create_session(
- {
- "model": "claude-haiku-4.5",
- "available_tools": [],
- }
+ PermissionHandler.approve_all,
+ "claude-haiku-4.5",
+ available_tools=[],
)
# 2. Send the secret word
diff --git a/test/scenarios/sessions/streaming/python/main.py b/test/scenarios/sessions/streaming/python/main.py
index 2bbc94e78..bbe99ba23 100644
--- a/test/scenarios/sessions/streaming/python/main.py
+++ b/test/scenarios/sessions/streaming/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
async def main():
@@ -11,10 +11,9 @@ async def main():
try:
session = await client.create_session(
- {
- "model": "claude-haiku-4.5",
- "streaming": True,
- }
+ PermissionHandler.approve_all,
+ "claude-haiku-4.5",
+ streaming=True,
)
chunk_count = 0
diff --git a/test/scenarios/tools/custom-agents/python/main.py b/test/scenarios/tools/custom-agents/python/main.py
index d4e416716..bb1e8cc2d 100644
--- a/test/scenarios/tools/custom-agents/python/main.py
+++ b/test/scenarios/tools/custom-agents/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
async def main():
@@ -11,18 +11,17 @@ async def main():
try:
session = await client.create_session(
- {
- "model": "claude-haiku-4.5",
- "custom_agents": [
- {
- "name": "researcher",
- "display_name": "Research Agent",
- "description": "A research agent that can only read and search files, not modify them",
- "tools": ["grep", "glob", "view"],
- "prompt": "You are a research assistant. You can search and read files but cannot modify anything. When asked about your capabilities, list the tools you have access to.",
- },
- ],
- }
+ PermissionHandler.approve_all,
+ "claude-haiku-4.5",
+ custom_agents=[
+ {
+ "name": "researcher",
+ "display_name": "Research Agent",
+ "description": "A research agent that can only read and search files, not modify them",
+ "tools": ["grep", "glob", "view"],
+ "prompt": "You are a research assistant. You can search and read files but cannot modify anything. When asked about your capabilities, list the tools you have access to.",
+ },
+ ],
)
response = await session.send_and_wait(
diff --git a/test/scenarios/tools/mcp-servers/python/main.py b/test/scenarios/tools/mcp-servers/python/main.py
index 81d2e39ba..ba7494839 100644
--- a/test/scenarios/tools/mcp-servers/python/main.py
+++ b/test/scenarios/tools/mcp-servers/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
async def main():
@@ -22,8 +22,7 @@ async def main():
"args": args,
}
- session_config = {
- "model": "claude-haiku-4.5",
+ session_kwargs = {
"available_tools": [],
"system_message": {
"mode": "replace",
@@ -31,9 +30,11 @@ async def main():
},
}
if mcp_servers:
- session_config["mcp_servers"] = mcp_servers
+ session_kwargs["mcp_servers"] = mcp_servers
- session = await client.create_session(session_config)
+ session = await client.create_session(
+ PermissionHandler.approve_all, "claude-haiku-4.5", **session_kwargs
+ )
response = await session.send_and_wait(
{"prompt": "What is the capital of France?"}
diff --git a/test/scenarios/tools/no-tools/python/main.py b/test/scenarios/tools/no-tools/python/main.py
index d857183c0..5a3079e1c 100644
--- a/test/scenarios/tools/no-tools/python/main.py
+++ b/test/scenarios/tools/no-tools/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
SYSTEM_PROMPT = """You are a minimal assistant with no tools available.
You cannot execute code, read files, edit files, search, or perform any actions.
@@ -16,11 +16,10 @@ async def main():
try:
session = await client.create_session(
- {
- "model": "claude-haiku-4.5",
- "system_message": {"mode": "replace", "content": SYSTEM_PROMPT},
- "available_tools": [],
- }
+ PermissionHandler.approve_all,
+ "claude-haiku-4.5",
+ system_message={"mode": "replace", "content": SYSTEM_PROMPT},
+ available_tools=[],
)
response = await session.send_and_wait(
diff --git a/test/scenarios/tools/skills/python/main.py b/test/scenarios/tools/skills/python/main.py
index 5adb74b76..ab4ee08f9 100644
--- a/test/scenarios/tools/skills/python/main.py
+++ b/test/scenarios/tools/skills/python/main.py
@@ -15,14 +15,12 @@ async def main():
skills_dir = str(Path(__file__).resolve().parent.parent / "sample-skills")
session = await client.create_session(
- {
- "model": "claude-haiku-4.5",
- "skill_directories": [skills_dir],
- "on_permission_request": lambda _: {"kind": "approved"},
- "hooks": {
- "on_pre_tool_use": lambda _: {"permission_decision": "allow"},
- },
- }
+ lambda _, __: {"kind": "approved"},
+ "claude-haiku-4.5",
+ skill_directories=[skills_dir],
+ hooks={
+ "on_pre_tool_use": lambda _, __: {"permissionDecision": "allow"},
+ },
)
response = await session.send_and_wait(
diff --git a/test/scenarios/tools/tool-filtering/python/main.py b/test/scenarios/tools/tool-filtering/python/main.py
index 174be620e..66e8baee3 100644
--- a/test/scenarios/tools/tool-filtering/python/main.py
+++ b/test/scenarios/tools/tool-filtering/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
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."""
@@ -13,11 +13,10 @@ async def main():
try:
session = await client.create_session(
- {
- "model": "claude-haiku-4.5",
- "system_message": {"mode": "replace", "content": SYSTEM_PROMPT},
- "available_tools": ["grep", "glob", "view"],
- }
+ PermissionHandler.approve_all,
+ "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/virtual-filesystem/python/main.py b/test/scenarios/tools/virtual-filesystem/python/main.py
index b150c1a2a..526411228 100644
--- a/test/scenarios/tools/virtual-filesystem/python/main.py
+++ b/test/scenarios/tools/virtual-filesystem/python/main.py
@@ -53,13 +53,11 @@ async def main():
try:
session = await client.create_session(
- {
- "model": "claude-haiku-4.5",
- "available_tools": [],
- "tools": [create_file, read_file, list_files],
- "on_permission_request": auto_approve_permission,
- "hooks": {"on_pre_tool_use": auto_approve_tool},
- }
+ auto_approve_permission,
+ "claude-haiku-4.5",
+ available_tools=[],
+ tools=[create_file, read_file, list_files],
+ hooks={"on_pre_tool_use": auto_approve_tool},
)
response = await session.send_and_wait(
diff --git a/test/scenarios/transport/reconnect/python/main.py b/test/scenarios/transport/reconnect/python/main.py
index e8aecea50..5e76db1e2 100644
--- a/test/scenarios/transport/reconnect/python/main.py
+++ b/test/scenarios/transport/reconnect/python/main.py
@@ -1,7 +1,7 @@
import asyncio
import os
import sys
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
async def main():
@@ -12,7 +12,7 @@ async def main():
try:
# First session
print("--- Session 1 ---")
- session1 = await client.create_session({"model": "claude-haiku-4.5"})
+ session1 = await client.create_session(PermissionHandler.approve_all, "claude-haiku-4.5")
response1 = await session1.send_and_wait(
{"prompt": "What is the capital of France?"}
@@ -29,7 +29,7 @@ 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(PermissionHandler.approve_all, "claude-haiku-4.5")
response2 = await session2.send_and_wait(
{"prompt": "What is the capital of France?"}
diff --git a/test/scenarios/transport/stdio/python/main.py b/test/scenarios/transport/stdio/python/main.py
index 138bb5646..cfe12901f 100644
--- a/test/scenarios/transport/stdio/python/main.py
+++ b/test/scenarios/transport/stdio/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
async def main():
@@ -10,7 +10,7 @@ async def main():
client = CopilotClient(opts)
try:
- session = await client.create_session({"model": "claude-haiku-4.5"})
+ session = await client.create_session(PermissionHandler.approve_all, "claude-haiku-4.5")
response = await session.send_and_wait(
{"prompt": "What is the capital of France?"}
diff --git a/test/scenarios/transport/tcp/python/main.py b/test/scenarios/transport/tcp/python/main.py
index 05aaa9270..a47c926e9 100644
--- a/test/scenarios/transport/tcp/python/main.py
+++ b/test/scenarios/transport/tcp/python/main.py
@@ -1,6 +1,6 @@
import asyncio
import os
-from copilot import CopilotClient
+from copilot import CopilotClient, PermissionHandler
async def main():
@@ -9,7 +9,7 @@ async def main():
})
try:
- session = await client.create_session({"model": "claude-haiku-4.5"})
+ session = await client.create_session(PermissionHandler.approve_all, "claude-haiku-4.5")
response = await session.send_and_wait(
{"prompt": "What is the capital of France?"}