Skip to content

Add Python cloud session SDK API#1258

Closed
JasonEtco wants to merge 1 commit into
mainfrom
jasonetco/python-cloud-session-sdk
Closed

Add Python cloud session SDK API#1258
JasonEtco wants to merge 1 commit into
mainfrom
jasonetco/python-cloud-session-sdk

Conversation

@JasonEtco
Copy link
Copy Markdown

Summary

Implements the cloud session SDK API for the Python SDK, matching the Node SDK behavior from #1256.

Changes

New copilot.cloud sub-package

  • types.py — All cloud session types: CloudRepository, CloudSessionOptions, CloudConnectOptions, CloudSessionMetadata, MissionControlTask, MissionControlCommandType enum, steering payloads, and event types.
  • mission_control_client.py — HTTP client for Mission Control REST API (create_cloud_task, list_task_events, steer_task, get_task, get_frontend_url). Uses urllib.request (stdlib). Includes CloudSessionError with typed failure reasons.
  • cloud_session.pyCloudSession class with connect/disconnect lifecycle, event handlers, send/abort/steering helpers, background event polling with deduplication.

CopilotClient additions

  • create_cloud_session(options) — Creates a sandbox-backed cloud session. Requires explicit repository or owner.
  • connect_cloud_session(task_id, options) — Attaches to an existing Mission Control task.

Design decisions

  • No new dependencies (stdlib urllib.request in executor)
  • Uses X-Copilot-Agent-Slug: copilot-developer-sandbox
  • No Git inference — callers pass explicit repository/owner
  • Wire format uses camelCase, Python API uses snake_case

Tests

13 unit tests covering task creation, repo-less tasks, owner validation, steer API, event sorting/dedup, error handling, typed handlers, unsubscribe, and context manager. All 75 tests pass.

Implement cloud session support in the Python SDK, matching the Node SDK
behavior from PR #1256. This adds:

- copilot.cloud sub-package with CloudSession, MissionControlClient, and
  all related types
- CopilotClient.create_cloud_session() for creating sandbox-backed cloud
  sessions through Mission Control
- CopilotClient.connect_cloud_session() for attaching to existing tasks
- Event polling with deduplication and chronological sorting
- Steering helpers: send, abort, respond_to_permission,
  respond_to_ask_user, respond_to_elicitation, respond_to_exit_plan_mode,
  switch_mode
- MissionControlCommandType enum covering user_message, abort,
  permission_response, ask_user_response, plan_approval_response,
  elicitation_response, and mode_switch
- 13 unit tests covering task creation, repo-less tasks, owner
  validation, steer API, event sorting/dedup, error handling, typed
  handlers, and context manager support

Uses X-Copilot-Agent-Slug: copilot-developer-sandbox.
Requires callers to pass explicit repository or owner (no Git inference).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 11, 2026 15:33
@JasonEtco JasonEtco requested a review from a team as a code owner May 11, 2026 15:33
# ------------------------------------------------------------------

@overload
def on(self, handler: CloudSessionEventHandler, /) -> Callable[[], None]: ...
def on(self, handler: CloudSessionEventHandler, /) -> Callable[[], None]: ...

@overload
def on(self, event_type: str, handler: CloudSessionEventHandler, /) -> Callable[[], None]: ...
await self._poll_events()
except Exception as exc:
self._report_poll_error(exc)
except asyncio.CancelledError:
for handler in list(typed_handlers):
try:
handler(event)
except Exception:
for handler in list(self._event_handlers):
try:
handler(event)
except Exception:
error_body = ""
try:
error_body = http_err.read().decode("utf-8")
except Exception:
msg = parsed.get("message")
if isinstance(msg, str) and msg:
return msg
except json.JSONDecodeError:
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a Python implementation of the “cloud session” SDK API (Mission Control task creation/attachment, event polling, and steering), aligning the Python SDK with the behavior introduced in the Node SDK (per #1256).

Changes:

  • Introduces a new copilot.cloud subpackage with cloud-session types, a Mission Control REST client, and a CloudSession polling/steering wrapper.
  • Extends CopilotClient with create_cloud_session(...) and connect_cloud_session(...) entry points.
  • Adds a dedicated unit test suite covering creation/attachment, polling/dedup, steering, and lifecycle behaviors.
Show a summary per file
File Description
python/copilot/cloud/types.py Defines cloud session/task/event types and option payloads for the Python API.
python/copilot/cloud/mission_control_client.py Implements Mission Control REST calls (create task, list events, steer, get task, frontend URL).
python/copilot/cloud/cloud_session.py Adds CloudSession lifecycle, polling/dedup logic, subscriptions, and steering helpers.
python/copilot/cloud/init.py Exposes the cloud session API surface from the new subpackage.
python/copilot/client.py Adds CopilotClient cloud-session creation/connection helpers and env-driven configuration.
python/copilot/init.py Re-exports cloud session symbols from the package root.
python/test_cloud_session.py Adds unit coverage for the new cloud session API and behaviors.

Copilot's findings

  • Files reviewed: 7/7 changed files
  • Comments generated: 5

Comment on lines +129 to +134
async def list_task_events(self, task_id: str) -> list[CloudSessionEvent]:
"""Poll task events from Mission Control."""
encoded_id = urllib.request.quote(task_id, safe="")
data = await self._request_json(
f"{self._base_url}/tasks/{encoded_id}/events",
method="GET",
Comment on lines +264 to +269
def _sync_request() -> str:
data = body.encode("utf-8") if body else None
req = urllib.request.Request(url, data=data, headers=headers, method=method)
try:
with urllib.request.urlopen(req) as resp:
return resp.read().decode("utf-8")
Comment thread python/copilot/client.py
Comment on lines +2153 to +2157
owner=owner,
repository=repository,
created_at=datetime.fromisoformat(task.created_at),
updated_at=datetime.fromisoformat(task.updated_at),
state=task.state,
Comment on lines +408 to +414
typed_handlers = self._typed_event_handlers.get(event.type)
if typed_handlers:
for handler in list(typed_handlers):
try:
handler(event)
except Exception:
pass
Comment on lines +351 to +355
async def _wait_for_initial_events(self) -> list[CloudSessionEvent]:
deadline = asyncio.get_event_loop().time() + self._initial_event_timeout_s
while True:
events = await self._client.list_task_events(self.metadata.task_id)
if events:
@JasonEtco JasonEtco closed this May 11, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Cross-SDK Consistency Review 🔍

This PR adds the cloud session API to the Python SDK, mirroring the Node.js implementation from #1256. The implementation looks well-structured and follows Python conventions (snake_case, dataclasses, TypedDict, async/await).

Summary of new API surface

Python Node.js equivalent
client.create_cloud_session(options) client.createCloudSession(options)
client.connect_cloud_session(task_id, options) client.connectCloudSession(taskId, options)
CloudSession.send(prompt=...) session.send({ prompt })
CloudSession.send_and_wait(prompt=...) session.sendAndWait({ prompt })
CloudSession.abort() session.abort()
CloudSession.disconnect() / destroy() session.disconnect() / destroy()
CloudSession.on(handler) / on(type, handler) session.on(handler) / on(type, handler)
CloudSession.respond_to_permission(payload) session.respondToPermission(payload)
CloudSession.respond_to_ask_user(payload) session.respondToAskUser(payload)
CloudSession.respond_to_elicitation(payload) session.respondToElicitation(payload)
CloudSession.respond_to_exit_plan_mode(payload) session.respondToExitPlanMode(payload)
CloudSession.switch_mode(payload) session.switchMode(payload)

Python and Node naming is consistent, properly applying each language's conventions. ✅


⚠️ Cross-SDK Feature Gap: Go and .NET

The Go and .NET SDKs do not yet have an equivalent cloud session API. Once Node (#1256) and Python (#1258) land, those two SDKs will be missing:

  • Go would need: client.CreateCloudSession(options) and client.ConnectCloudSession(taskID, options) returning a *CloudSession, plus a MissionControlClient, cloud types, and event polling.
  • .NET would need: client.CreateCloudSessionAsync(options) and client.ConnectCloudSessionAsync(taskId, options) returning a CloudSession, plus equivalent types.

This isn't a blocker for this PR, but it would be worth tracking follow-up issues to bring Go and .NET to parity.


Minor observation

CloudSession.get_messages() (returns all events received so far) doesn't appear to have a direct equivalent listed in the Node SDK public API. If it's Node-only or was intentionally omitted, that's fine — just worth confirming the method name and presence is consistent across both implementations.

Generated by SDK Consistency Review Agent for issue #1258 · ● 796.2K ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants