Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,18 @@ class GitHubCopilotSettings(TypedDict, total=False):
Can be set via environment variable GITHUB_COPILOT_TIMEOUT.
log_level: CLI log level.
Can be set via environment variable GITHUB_COPILOT_LOG_LEVEL.
copilot_home: Directory where the CLI stores session state, configuration,
and other persistent data. Can be set via environment variable
GITHUB_COPILOT_COPILOT_HOME. Defaults to ~/.copilot when not set.
Only applicable when the SDK spawns the CLI process (ignored when
connecting to an external server via a pre-configured client).
"""

cli_path: str | None
model: str | None
timeout: float | None
log_level: str | None
copilot_home: str | None


class GitHubCopilotOptions(TypedDict, total=False):
Expand Down Expand Up @@ -187,6 +193,12 @@ class GitHubCopilotOptions(TypedDict, total=False):
instead of the default GitHub Copilot backend.
"""

instruction_directories: list[str]
"""Additional directories to search for custom instruction files.
Lets applications point the CLI at project-specific or team-shared instruction
files beyond the default locations.
"""

on_function_approval: FunctionApprovalCallback
"""Approval callback for ``FunctionTool`` instances declared with
``approval_mode="always_require"``. The callback is awaited (sync or async)
Expand Down Expand Up @@ -300,7 +312,9 @@ def __init__(
on_permission_request: PermissionHandlerType | None = opts.pop("on_permission_request", None)
mcp_servers: dict[str, MCPServerConfig] | None = opts.pop("mcp_servers", None)
provider: ProviderConfig | None = opts.pop("provider", None)
instruction_directories: list[str] | None = opts.pop("instruction_directories", None)
on_function_approval: FunctionApprovalCallback | None = opts.pop("on_function_approval", None)
copilot_home = opts.pop("copilot_home", None)

self._settings = load_settings(
GitHubCopilotSettings,
Expand All @@ -309,6 +323,7 @@ def __init__(
model=model,
timeout=timeout,
log_level=log_level,
copilot_home=copilot_home,
env_file_path=env_file_path,
env_file_encoding=env_file_encoding,
)
Expand All @@ -318,6 +333,7 @@ def __init__(
self._function_approval_handler: FunctionApprovalCallback | None = on_function_approval
self._mcp_servers = mcp_servers
self._provider = provider
self._instruction_directories = instruction_directories
self._default_options = opts
self._started = False

Expand Down Expand Up @@ -346,10 +362,13 @@ async def start(self) -> None:
if self._client is None:
cli_path = self._settings.get("cli_path") or None
log_level = self._settings.get("log_level") or None
copilot_home = self._settings.get("copilot_home") or None

subprocess_kwargs: dict[str, Any] = {"cli_path": cli_path}
if log_level:
subprocess_kwargs["log_level"] = log_level
if copilot_home:
subprocess_kwargs["copilot_home"] = copilot_home
self._client = CopilotClient(SubprocessConfig(**subprocess_kwargs))

try:
Expand Down Expand Up @@ -523,13 +542,14 @@ async def _run_impl(
# send_and_wait returns only the final ASSISTANT_MESSAGE event;
# other events (deltas, tool calls) are handled internally by the SDK.
if response_event and response_event.type == SessionEventType.ASSISTANT_MESSAGE:
message_id = response_event.data.message_id
data: Any = response_event.data
message_id = data.message_id

if response_event.data.content:
if data.content:
response_messages.append(
Message(
role="assistant",
contents=[Content.from_text(response_event.data.content)],
contents=[Content.from_text(data.content)],
message_id=message_id,
raw_representation=response_event,
)
Expand Down Expand Up @@ -603,12 +623,13 @@ async def _stream_updates(

def event_handler(event: SessionEvent) -> None:
if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA:
if event.data.delta_content:
data: Any = event.data
if data.delta_content:
update = AgentResponseUpdate(
role="assistant",
contents=[Content.from_text(event.data.delta_content)],
response_id=event.data.message_id,
message_id=event.data.message_id,
contents=[Content.from_text(data.delta_content)],
response_id=data.message_id,
message_id=data.message_id,
raw_representation=event,
)
queue.put_nowait(update)
Expand Down Expand Up @@ -652,7 +673,8 @@ def event_handler(event: SessionEvent) -> None:
elif event.type == SessionEventType.SESSION_IDLE:
queue.put_nowait(None)
elif event.type == SessionEventType.SESSION_ERROR:
error_msg = event.data.message or "Unknown error"
error_data: Any = event.data
error_msg = error_data.message or "Unknown error"
queue.put_nowait(AgentException(f"GitHub Copilot session error: {error_msg}"))

unsubscribe = copilot_session.on(event_handler)
Expand Down Expand Up @@ -838,7 +860,7 @@ async def _get_or_create_session(

try:
if agent_session.service_session_id:
return await self._resume_session(agent_session.service_session_id, streaming)
return await self._resume_session(agent_session.service_session_id, streaming, runtime_options)

session = await self._create_session(streaming, runtime_options)
agent_session.service_session_id = session.session_id
Expand Down Expand Up @@ -868,6 +890,7 @@ async def _create_session(
)
mcp_servers = opts.get("mcp_servers") or self._mcp_servers or None
provider = opts.get("provider") or self._provider or None
instruction_directories = opts.get("instruction_directories", self._instruction_directories)
tools = self._prepare_tools(self._tools) if self._tools else None

Comment thread
giles17 marked this conversation as resolved.
return await self._client.create_session(
Expand All @@ -878,23 +901,46 @@ async def _create_session(
tools=tools or None,
mcp_servers=mcp_servers or None,
provider=provider or None,
instruction_directories=instruction_directories,
)

async def _resume_session(self, session_id: str, streaming: bool) -> CopilotSession:
"""Resume an existing Copilot session by ID."""
async def _resume_session(
self,
session_id: str,
streaming: bool,
runtime_options: dict[str, Any] | None = None,
) -> CopilotSession:
"""Resume an existing Copilot session by ID.

Args:
session_id: The session ID to resume.
streaming: Whether to enable streaming for the session.
runtime_options: Runtime options that take precedence over default_options.
"""
if not self._client:
raise RuntimeError("GitHub Copilot client not initialized. Call start() first.")

permission_handler: PermissionHandlerType = self._permission_handler or _deny_all_permissions
opts = runtime_options or {}
model = opts.get("model") or self._settings.get("model") or None
system_message = opts.get("system_message") or self._default_options.get("system_message") or None
permission_handler: PermissionHandlerType = (
opts.get("on_permission_request") or self._permission_handler or _deny_all_permissions
)
mcp_servers = opts.get("mcp_servers") or self._mcp_servers or None
provider = opts.get("provider") or self._provider or None
instruction_directories = opts.get("instruction_directories", self._instruction_directories)
tools = self._prepare_tools(self._tools) if self._tools else None

return await self._client.resume_session(
session_id,
on_permission_request=permission_handler,
streaming=streaming,
model=model or None,
system_message=system_message or None,
tools=tools or None,
mcp_servers=self._mcp_servers or None,
provider=self._provider or None,
mcp_servers=mcp_servers or None,
provider=provider or None,
instruction_directories=instruction_directories,
)


Expand Down
2 changes: 1 addition & 1 deletion python/packages/github_copilot/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ classifiers = [
]
dependencies = [
"agent-framework-core>=1.2.2,<2",
"github-copilot-sdk>=0.2.1,<=0.2.1; python_version >= '3.11'",
"github-copilot-sdk>=1.0.0b2,<=1.0.0b2; python_version >= '3.11'",
]

[tool.uv]
Expand Down
Loading
Loading