feat: implement Python SDK v5 - #8
Draft
varin-nair-factory wants to merge 11 commits into
Draft
Conversation
Add the typed high-level API, exact low-level namespace, lifecycle-safe streaming and sessions, MCP, observability, documentation, examples, and comprehensive tests. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Core install now depends only on pydantic and typing-extensions; mcp/starlette/uvicorn move to the "droid-sdk[mcp]" extra. Importing droid_sdk.mcp without the extra raises a clear ImportError, and external MCP server configs stay importable from the package root. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
DroidClient no longer imports InteractionDispatcher or carries a second handler mechanism. Session owns one dispatcher and registers it through the existing set_permission_handler/set_ask_user_handler slots, swapping only the dispatcher error sink per turn. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Session and streaming each carried their own copies of the wire<->public converters (settings, MCP status, usage, notification extraction), and the copies had already diverged: session's settings merge re-parsed raw camelCase dicts by hand with per-field fallbacks while streaming validated typed payloads. All shared conversions now live in _high_level/_convert.py with one strictly typed implementation per concept, and the settings merge goes through the validated SettingsUpdatedPayload model with model_fields_set deciding which explicit nulls clear a value. The third copy of _consume_task_result (transport, session, streaming) moves to droid_sdk/_util.py. The session test fixture now builds a real wire SessionSettings model instead of a SimpleNamespace so converter changes are exercised against the actual schema. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…t hook The open, close, and replacement paths each hand-rolled the same cancellation-safety idiom: shield-await a shared task, run compensation when the caller is cancelled, and cancel-then-drain tasks that must not outlive their operation. Those idioms now live once in droid_sdk._util (wait_shielded, cancel_and_drain), and the replacement path's inline compensation becomes the named _abandon_cancelled_replacement method. The module-global _replacement_handoff_checkpoint existed only so tests could monkeypatch it. The cancellation delivery point is now an instance seam (Session._replacement_checkpoint, defaulting to _util.cancellation_checkpoint) that tests gate per session without touching module state. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Successor sessions were cloned with object.__new__ plus a __dict__ copy and a trailing list of field resets, so every new Session attribute silently leaked into successors unless someone remembered to reset it here. The successor is now built through Session's real constructor, and _create_successor names exactly which state carries over: load policies, and the live wiring (client, dispatcher, in-process MCP servers) the successor adopts. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
session.py fused the lifecycle state machine with every request/response operation the session exposes. The plain one-call-one-request methods (settings, tools, skills, MCP management, context, spec mode) and the replacement-producing operations (fork, compact, rewind) now live in SessionOperationsMixin in _session_operations.py, with the state and helpers they rely on declared explicitly at the top of the mixin. session.py keeps the cohesive open/close/replacement state machine, streaming turn management, and run(), dropping from 1,998 lines at review time to 1,166. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
client.py disabled reportCallIssue, reportUnknownMemberType, and reportUnknownVariableType for the whole file. The unknown-type escapes were four real gaps in _dispatch_notification's payload probing, fixed with explicit narrowing. What remains is only reportCallIssue, which pyright raises because its dataclass_transform view of the request-param models sees the camelCase wire aliases instead of the snake_case names that populate_by_name accepts; mypy's pydantic plugin validates those calls. The header now documents exactly that. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
SdkMcpServer smuggled mutable runtime state through object-typed fields on a frozen dataclass, forcing droid_sdk.mcp to read and write them via object.__getattribute__/__setattr__ plus casts. The handle is now honestly frozen: droid_sdk.mcp keeps a WeakKeyDictionary of typed per-server state, so the attribute smuggling and its casts are gone. The handle drops slots (weak references need __weakref__) and field equality (registry keys need an identity hash; the field-based hash was already unusable on unhashable tool schemas). Also port the server start/close single-flight to the shared wait_shielded/cancel_and_drain helpers, and name RunStream's finish grace period (_FINISH_GRACE_SECONDS) instead of an inline 0.1. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Full-branch simplify pass. The timezone-awareness validator was copied verbatim in config.py and messages.py; it now lives once in _immutable as ensure_aware. The three terminal result dataclasses repeated the same messages/structured_output freezing in their __post_init__, now shared as _seal_terminal_result. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Collapse the repeated-type continuation rows in the field and enum tables into one row per type, remove the duplicated SessionSettingsUpdate and MCP method tables, fold the two attachment examples into one, and trim prose that restated adjacent tables. No factual changes; claims re-verified against the source. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Validation