Skip to content

feat(copilot_sdk): support Copilot SDK 1.x - #301

Open
SriharshaEtigowni wants to merge 5 commits into
GitHubSecurityLab:mainfrom
SriharshaEtigowni:feat/copilot-sdk-v1
Open

SriharshaEtigowni wants to merge 5 commits into
GitHubSecurityLab:mainfrom
SriharshaEtigowni:feat/copilot-sdk-v1

Conversation

@SriharshaEtigowni

@SriharshaEtigowni SriharshaEtigowni commented Oct 30, 2026 •

Copy link
Copy Markdown

Summary

The Copilot backend was constrained to github-copilot-sdk>=0.2.2,<0.3.
That adapter's client configuration, system-message, event and permission APIs
are incompatible with the released SDK 1.x interface and newer Copilot runtimes.

This PR pins the published, non-prerelease github-copilot-sdk==1.0.28 and
updates the adapter using public SDK APIs only, without runtime monkey patches.
No equivalent open upstream migration PR was identified during preparation.

Implementation

  • Use keyword-based CopilotClient initialization. Resolve explicit credentials
    from the named environment variable, disable logged-in-user fallback for an
    explicit token, and fail clearly for missing credentials or failed authentication.
    Otherwise preserve the SDK's default authentication chain.
  • Pass the requested model and taskflow system instructions to the session.
    Validate concrete-model availability/policy, reject unintended model changes,
    accept authenticated auto routing even with a concrete-only catalogue, and
    support the SDK's max reasoning effort.
  • Correlate streaming events with the submitted message, suppress duplicate final
    text and sub-agent output, preserve token accounting, and translate authentication,
    model, rate-limit, timeout and policy errors into backend errors. Explicitly
    foreign full assistant messages are ignored before output or model validation.
    Discard stale messages and usage before sending a new request, while preserving
    queued session-start/model-change checks and active-response model enforcement.
  • Register public SDK Tool handlers backed by the runner's connected MCP wrappers.
    This preserves stdio/SSE/HTTP transport handling, namespaces, blocked-tool
    filtering and toolbox confirmations, without starting duplicate MCP servers.
    Terminal tools enforce exclude_from_context before a follow-up model call.
  • Disable tool deferral and large-output file offloading, so configured tools
    and their results remain accessible without enabling SDK search/file tools.
  • Fetch all MCP tool-list pages through the existing wrapper, detecting repeated
    cursors. Prefer structured result payloads, including empty objects, while
    honoring FastMCP's explicitly declared non-object result envelopes so existing
    memcache/repeat-prompt behavior is preserved.
  • Report refused confirmations as MCP errors/SDK failures without invoking the
    tool. Keep unfiltered pagination inside one serialized connection for
    reconnecting stdio toolboxes, with cleanup on connection/listing errors and
    cancellation; ordinary transports retain their runner-owned lifecycle.
  • Distinguish unavailable confirmation prompts from MCP transport failures.
    Only prompt I/O errors become confirmation denials; transport errors retain
    their original exception and use the SDK's normal tool-error handling.
  • Abort incomplete sends/streams and stop the client on failed setup or cleanup,
    including cancellation. Disable ambient instructions, hooks, skills and memory.

Follow-up commit 0af51257f8a4b7b9b376592ad4914c0029e8f7be addresses all six
SDK/MCP review comments and adds 14 offline regression cases.
Commit e00214c0256996f410d7ccad84d1ae270067dae1 addresses the subsequent
confirmation-result and reconnecting-toolbox findings with 8 additional cases.
Commit 5c1aaf50c777a9fa9f68b9879840117223cb4ee3 fixes pre-send validation of
stale foreign-model responses and adds 2 regression cases.
Commit 3ad4fc2a3e0052ad082a3a117ae88727d0e23528 fixes misleading confirmation
denials for transport I/O failures and adds 5 regression cases.

Compatibility and security

Breaking Python compatibility change: SDK 1.0.28 requires Python >=3.11.
Update requires-python, classifiers and the README accordingly, rather than
advertising an uninstallable Python 3.10 combination. This ends Python 3.10
support for the entire package. The existing CI matrix already covers 3.11/3.14;
the existing Ruff syntax baseline is retained to avoid unrelated source rewrites.

Only registered taskflow tools are exposed through a source-qualified allowlist.
Permission approval authorizes dispatch to the policy-enforcing wrapper, not
unconditional execution. Toolbox confirmations are retained; headless: true
remains the explicit existing opt-out. Raw MCP requests, unknown tools, SDK
built-in tools and managed-approval requests are denied. Unmanaged MCP specs
without the runner-provided wrapper fail closed.

The security review identified the following retained upstream issue, now fixed.
The line reference describes the implementation on the base main commit.

# Severity File Lines Vulnerability Confidence
1 🟠 HIGH src/seclab_taskflow_agent/sdk/copilot_sdk/permissions.py 41-51 MCP execution bypassed toolbox confirmations; fixed by routing through existing wrappers 9/10

Independent reviews through e00214c found no remaining security
vulnerabilities. No credential values, local configuration or generated artifacts
are included. Unrelated dependencies are unchanged. Shared MCP tool discovery now
exhausts pagination for its callers, including reconnecting transports. Shared
confirmation refusals now correctly report failure; runner/type edits remain
documentation-only.

Validation results

Current offline results cover 3ad4fc2; earlier live/package checks are labeled separately.

Check Result
CI-equivalent offline suite, Linux Python 3.11.17 643 passed, 2 skipped, 0 failed
CI-equivalent offline suite, Linux Python 3.14.9 643 passed, 2 skipped, 0 failed
Copilot, shared MCP and transport regression files, Python 3.12.15 144 passed, 0 failed
hatch fmt --linter --check Passed, repository-wide
Adapter/test/shared SDK-type formatting Passed, 8 files; both SDK/test files changed by the latest fix rechecked
Shared MCP transport/wrapper/test formatting Pre-existing differences retained, identical to the pre-follow-up commit
Focused mypy, with actual SDK dependencies Passed, 7 files
Shared wrapper typing 2 pre-existing errors, included in the full check and unchanged from the baseline
Full configured mypy arguments Failed: 84 pre-existing errors in 22 files, zero new diagnostics
pip check Passed, no broken requirements
Wheel/sdist build Both passed at e00214c; build configuration unchanged
Wheel/sdist Core Metadata validation with packaging==26.3 Both passed at e00214c; build configuration unchanged
twine check --strict Known baseline tooling failure, not rerun for this follow-up: Twine 6.1/6.2 reject Metadata-Version 2.5
Wheel metadata Python >=3.11 and SDK ==1.0.28 verified at e00214c; Python 3.10 excluded
Original Copilot smoke taskflow, gpt-5.6-luna Previously passed at 0af5125 on one unchanged retry; model variability detailed below
Same smoke prompts with reconnecting: true, gpt-5.6-luna Passed first attempt at e00214c, actual responses and three separate MCP process starts verified; not rerun for the last two targeted fixes

The full mypy diagnostics were compared with an untouched main snapshot in
the same SDK 1.x environment: upstream produced 98 errors in 25 files, and every
remaining diagnostic also occurred upstream. Unrelated typing issues were not
folded into this PR. The error-classification fix was checked against its parent 5c1aaf5:
both produced the same 84 errors, accounting for shifted source lines, with zero new diagnostics. The comment-only
runner.py edit retains existing formatting differences, verified identical to
untouched main. The shared MCP implementation/test formatting differences were
also verified unchanged using Hatch's actual formatter configuration.

Hatchling 1.33 emits Core Metadata 2.5, which validates with packaging 26.3.
Twine 6.1 and 6.2 override the validator's supported versions with a list ending
at 2.4. The same failure was reproduced on a wheel built from untouched main;
no unrelated build configuration or dependency changes were added to mask it.

Regression coverage includes explicit/default credentials, real SDK constructor
validation and public event types, model propagation, session lifecycle,
streaming/final responses, auth/model errors, usage, actual offline memcache
registration/invocation, permission approval/rejection, cancellation, terminal
tools, and the existing runner/taskflow behaviors. Added review regressions cover
auto authentication/catalogues, foreign responses with matching or different
models, non-deferred tools, inline output configuration, empty/mixed structured
results, declared FastMCP envelopes without losing extra fields, and paginated
tool discovery with later-page blocking/confirmation, empty pages and cycles.
The second follow-up also covers refusal failures in terminal/nonterminal modes,
real persistent/reconnecting memcache round trips, and reconnect cleanup/lock
release after discovery errors, cancellation and connection failures.
The pre-send regression queues a foreign-model message and usage before send,
verifies only the new request's response/usage are emitted without aborting,
and retains pre-send rejection of session-start and session-model-change mismatches.
The latest cases distinguish unavailable stdin from transport I/O failures, both
with confirmations disabled and after successful confirmation, preserving the
original transport exception without weakening fail-closed confirmation behavior.

Both opt-in pytest live tests (test_openai_agents_live_smoke and
test_copilot_sdk_live_smoke) were intentionally skipped in the normal suite.
The existing taskflow was exercised separately. Windows/macOS CI, other Python
versions, live remote SSE/HTTP servers and CodeQL CI were not executed locally.

GitHub Actions at 3ad4fc2: Python CI
and CodeQL Advanced
are awaiting maintainer approval for this fork PR (action_required). No CI jobs
have executed yet, so no remote CI success is claimed.

Live smoke evidence

  • SDK: 1.0.28; Python: 3.12.15; SecLab package: 0.5.1;
    model: gpt-5.6-luna; backend: copilot_sdk.
  • Latest live-tested source committed as e00214c0256996f410d7ccad84d1ae270067dae1,
    based on ab02b1fe8ac3d94352332b2c739c579f0d5ae810.
  • Latest run: the existing examples.taskflows.backend_copilot_sdk prompts and
    task settings, with an outside-checkout toolbox copy adding only
    server_params.reconnecting: true, run on 2026-10-30.
  • Both tasks completed first try in session 29259f623cb5. Three separate MCP
    process starts verified discovery, setter and getter reconnections. MCP
    stored/retrieved [1, 2, 3]; the model returned that array, then 1, 2, 3
    for repeat prompts.
  • Six SDK usage events all reported gpt-5.6-luna and recorded
    2,355 input tokens and 250 output tokens.
  • The prior ordinary-transport run at 0af5125 passed in session 3f8e1fdd89b9.
    Its first attempt authenticated and completed both tasks, but the
    model passed {"value":[1,2,3]} to the setter instead of the requested array.
    It therefore did not produce the intended array/fan-out outcome. Recorded SDK
    tool arguments confirmed this was model output, not payload conversion.
    Repeating that unchanged flow once produced the passing ordinary-transport result; no
    model-specific data coercion or example-prompt changes were introduced.
  • Authentication used COPILOT_GITHUB_TOKEN, supplied process-locally from an
    existing environment credential. Its value was never displayed or committed.
    No environment credential values were found in the diff or smoke artifacts.

Reproduction

With Python 3.11 and 3.14 available, install the checkout and development tools
in a virtual environment:

python -m pip install -e . hatch pytest mypy twine
python -m pytest -q tests/test_sdk_copilot_adapter.py \
  tests/test_sdk_copilot_mcp.py tests/test_sdk_copilot_permissions.py \
  tests/test_mcp_utils.py tests/test_mcp_transport.py
env -u SECLAB_TASKFLOW_LIVE_OPENAI_TOKEN -u SECLAB_TASKFLOW_LIVE_COPILOT_TOKEN \
  PYTEST_XDIST_AUTO_NUM_WORKERS=2 \
  hatch test --python 3.11,3.14 --cover --randomize --parallel \
  --retries 2 --retry-delay 1
hatch fmt --linter --check
hatch fmt --formatter --check src/seclab_taskflow_agent/sdk/copilot_sdk \
  src/seclab_taskflow_agent/sdk/base.py \
  tests/test_sdk_copilot_adapter.py tests/test_sdk_copilot_mcp.py \
  tests/test_sdk_copilot_permissions.py
python -m mypy --follow-imports=silent src/seclab_taskflow_agent/sdk/copilot_sdk \
  tests/test_sdk_copilot_adapter.py tests/test_sdk_copilot_mcp.py \
  tests/test_sdk_copilot_permissions.py
python -m pip check
hatch build

The repository-wide typing limitation is reproducible with
python -m mypy --install-types --non-interactive src/seclab_taskflow_agent tests.
The pre-existing runner formatting differences can be seen with
hatch fmt --formatter --check src/seclab_taskflow_agent/runner.py.
The metadata-tooling limitation is reproducible with
python -m twine check --strict dist/* using Twine 6.1/6.2 and Hatchling 1.33.

For the live smoke, use an existing exported COPILOT_GITHUB_TOKEN entitled to Copilot
and access to gpt-5.6-luna. Keep the override and generated state outside the
checkout; the YAML contains an environment variable name, not a credential:

repo="$PWD"
workdir="$(mktemp -d)"
mkdir -p "$workdir/smoke_models" "$workdir/logs" "$workdir/data" "$workdir/copilot-home"
cat > "$workdir/smoke_models/luna.yaml" <<'YAML'
seclab-taskflow-agent:
  version: "1.0"
  filetype: model_config
models:
  cop: gpt-5.6-luna
model_settings:
  cop:
    backend: copilot_sdk
    token: COPILOT_GITHUB_TOKEN
YAML
(
  cd "$workdir"
  PYTHONPATH="$repo:$workdir" LOG_DIR="$workdir/logs" \
    XDG_DATA_HOME="$workdir/data" COPILOT_HOME="$workdir/copilot-home" \
    TASK_AGENT_LOGLEVEL=INFO \
    python -m seclab_taskflow_agent \
      -t examples.taskflows.backend_copilot_sdk -m smoke_models.luna
)

For the reconnecting variant, also copy the example taskflow and
src/seclab_taskflow_agent/toolboxes/memcache.yaml into that temporary module
directory. Set the copied toolbox's server_params.reconnecting to true,
point the copied taskflow's first toolbox entry at that temporary toolbox module,
and run the copied taskflow with the same Luna override. Keep the prompts and
other settings unchanged; the latest run above verified exactly these differences.

Remaining limitations

Handoffs, temperature and parallel_tool_calls remain unsupported. Copilot
selects its own wire protocol; api_type is not honored. A hard taskflow
max_turns/max_steps limit is not mapped to the SDK (pre-existing limitation).
SDK session disconnection releases runtime resources but preserves SDK session
history according to its normal lifecycle. The Python floor change requires
upstream acceptance; no legacy SDK/Python 3.10 adapter is maintained here.

Use public SDK 1.x APIs, preserve taskflow MCP confirmation policies, and cover authentication, model selection, streaming and cleanup with offline regressions.

BREAKING CHANGE: Python 3.11 or newer is required.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 30, 2026 20:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved SDK/MCP integration defects can make tools and outputs inaccessible, discard structured payloads, mix responses from different requests, and reject automatic model routing.

6 open issues
What changed in this PR

Migrates the Copilot backend to SDK 1.x while preserving the taskflow runner’s tool policies and backend-neutral interface.

Changes:

  • Pins Copilot SDK 1.0.28 and raises the package’s minimum Python version to 3.11.
  • Updates authentication, model validation, streaming, error translation, and session cleanup.
  • Routes MCP tools through existing policy-enforcing wrappers and expands regression coverage.
File Description
tests/​test_sdk_copilot_permissions.py Tests registered-tool approvals and default-deny permissions.
tests/​test_sdk_copilot_mcp.py Tests wrapper-backed tools, confirmations, results, and memcache integration.
tests/​test_sdk_copilot_adapter.py Expands authentication, streaming, model, and lifecycle coverage.
src/​seclab_taskflow_agent/​sdk/​copilot_sdk/​permissions.py Restricts approvals to registered taskflow tools.
src/​seclab_taskflow_agent/​sdk/​copilot_sdk/​mcp.py Registers connected MCP wrappers as SDK custom tools.
src/​seclab_taskflow_agent/​sdk/​copilot_sdk/​backend.py Migrates session setup and event handling to SDK 1.x.
src/​seclab_taskflow_agent/​sdk/​base.py Updates wrapper-ownership documentation only.
src/​seclab_taskflow_agent/​runner.py Clarifies shared-wrapper ownership in a comment only.
README.md Documents backend behavior and the Python compatibility change.
pyproject.toml Pins the SDK and updates Python requirements and classifiers.

Review effort: Balanced

Allow authenticated auto routing, keep configured tools and large outputs accessible, and reject foreign assistant messages before model checks.

Preserve structured MCP payloads and declared FastMCP result envelopes, paginate tool discovery through the policy wrapper, and add focused regression coverage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 30, 2026 21:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Declined confirmations are incorrectly reported as successful tool calls, and reconnecting toolboxes fail during setup because unfiltered tool listing requires an active session.

1 open issue
2 previously missed issues

In code that hasn't changed since last review

Medium severity Confirmation refusals are reported as successful tool calls

src/​seclab_taskflow_agent/​sdk/​copilot_sdk/​mcp.py:64

When a user declines confirmation, MCPNamespaceWrap.call_tool() returns a CallToolResult without setting isError, so this branch reports an unexecuted call as success. With exclude_from_context, the refusal can therefore finish the task as a successful terminal tool result. Propagate a distinct confirmation-rejected outcome from the wrapper and map it to result_type="denied", as already done for unavailable confirmation.

Medium severity Reconnecting toolboxes fail during unfiltered tool listing

src/​seclab_taskflow_agent/​sdk/​copilot_sdk/​mcp.py:94

Toolboxes configured with reconnecting: true now fail during Copilot setup: list_tools_unfiltered() requires an active session, but ReconnectingMCPServerStdio.connect() is deliberately a no-op (mcp_transport.py:286–302). This bypasses the transport's per-operation connection lifecycle and raises before tools can be registered. Add a transport-aware unfiltered listing path that opens and closes the connection while preserving pagination and blocked-tool filtering.

5 resolved since last review

Review effort: Balanced

Report declined confirmations as MCP errors rather than successful tool calls. Keep unfiltered tool pagination inside one serialized per-call connection for reconnecting transports, with cleanup on errors and cancellation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 30, 2026 22:09
@SriharshaEtigowni

Copy link
Copy Markdown
Author

Follow-up e00214c addresses both additional findings:

  • Refused toolbox confirmations now return isError=True; Copilot receives a failure instead of success, and the tool is not invoked. Covered in ordinary and terminal-tool modes.
  • Reconnecting stdio toolboxes now own one serialized connection for unfiltered, fully paginated discovery. Cleanup runs on success, listing errors, cancellation and connection failures. Existing persistent-server ownership and tool filters/confirmations are preserved.

The pagination fix from 0af5125 remains in the shared wrapper, now also exercised within reconnecting sessions. No built-in tools or extra permissions were enabled.

Validation: 137 focused tests passed; the full Linux suite passed 636 tests with 2 intentional live-test skips on each of Python 3.11.17 and 3.14.9. Lint, focused typing, dependency consistency and builds passed; full typing and shared formatting retain only verified baseline issues. The authenticated gpt-5.6-luna smoke with unchanged example prompts and reconnecting memcache passed first try: three separate MCP process starts, stored/retrieved [1,2,3], then model responses 1/2/3. Independent security review found no vulnerabilities.

The two new findings are not yet visible as published inline threads through the API, so their fixes are recorded here. The PR remains a draft; Python CI and CodeQL still require maintainer approval.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The pre-send drain validates stale assistant messages from other requests, so a queued foreign response with a different model can abort a retry before session.send().

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Pre-send drain rejects stale foreign-model responses

src/​seclab_taskflow_agent/​sdk/​copilot_sdk/​backend.py:368

The pre-send drain still applies model validation to full assistant messages from previous requests. If a foreign response reporting a different model is already queued when a retry starts, this raises before session.send(); the identical response arriving after send is correctly ignored by the origin filter below. Discard stale assistant-message events before model enforcement while retaining the session-level model checks, and add a regression with the foreign response in the pre-send queue.

1 resolved since last review

Review effort: Balanced

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 30, 2026 22:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Human assessment is needed because broad I/O exception handling can misreport MCP transport failures as confirmation denials even when confirmations are disabled, obscuring the actual connection error.

No open issues 🎉

Review effort: Balanced

@SriharshaEtigowni

Copy link
Copy Markdown
Author

Fixed the pre-send stale-response finding in 5c1aaf50c777a9fa9f68b9879840117223cb4ee3.

The drain now validates only root session-start/model-change events. Prior-request
assistant messages and usage are discarded before model enforcement; active-response
model checks remain unchanged. The regression queues a foreign response with a
different model before session.send(), verifies the new prompt is sent without
aborting, and checks that only the new response/usage are returned. Both queued
session-level mismatch types still reject the send.

Validation: 139 focused tests passed; 638 passed, 2 skipped on each of
Python 3.11.17 and 3.14.9. Repository lint, changed-file formatting, focused SDK
typing and dependency consistency passed. Full mypy still reports the same
84 baseline errors, with no new diagnostics.

The live Luna smoke was not repeated for this targeted fix; the last verified
live source is e00214c. The PR remains a draft, and GitHub Actions still require
maintainer approval.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 30, 2026 23:49
@SriharshaEtigowni

Copy link
Copy Markdown
Author

Addressed the human-assessment note about transport failures being mislabeled as
confirmation denials in 3ad4fc2a3e0052ad082a3a117ae88727d0e23528.

The concern was reproducible with both EOFError and OSError, with confirmations
disabled and after a successful confirmation. The shared wrapper now labels only
errors from the confirmation prompt with a dedicated exception. The Copilot
adapter converts only that exception into a denial. Actual transport failures
retain their original exception/message and follow normal SDK tool-error handling.
Unavailable confirmation input still prevents execution; no permission restrictions
were relaxed.

Validation: 144 focused tests passed; 643 passed, 2 skipped on each of
Python 3.11.17 and 3.14.9. Repository lint, SDK/test formatting, focused SDK typing
and dependency consistency passed. Full mypy's 84 baseline errors and the shared
wrapper's existing formatting differences are unchanged.

The live Luna smoke was not rerun for this targeted error-path fix; its last
verified source remains e00214c. The PR is still a draft, and GitHub Actions
await maintainer approval. No human review approval is claimed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Human assessment is needed to evaluate a subtle event-correlation race in which stale usage from an aborted turn can incorrectly abort a new request.

No open issues 🎉

Review effort: Balanced

@SriharshaEtigowni
SriharshaEtigowni marked this pull request as ready for review October 30, 2026 23:53
@SriharshaEtigowni
SriharshaEtigowni requested a review from a team as a code owner October 30, 2026 23:53

This branch has not been deployed

No deployments
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