feat(sdk)!: upgrade to SDK v0.2.0 with breaking API changes - #32
Conversation
BREAKING CHANGE: Requires github-copilot-sdk>=0.2.0,<0.3.0
BREAKING CHANGE: CopilotClient now uses SubprocessConfig
BREAKING CHANGE: session.send() signature changed to send(prompt, attachments=...)
SDK v0.2.0 Upgrade:
- SubprocessConfig replaces raw dict for client initialization
- send(prompt, attachments=...) replaces send({"prompt": ...})
- Vision support: ImageBlock → BlobAttachment passthrough
New Architecture:
- sdk_adapter/ quarantine isolates SDK imports
- config/ YAML files for policy values (Three-Medium)
- contracts/ behavioral specifications
Test Results:
- Windows: 858 passed, 2 skipped (Unix tests), 90.22% coverage
- WSL/Linux: 859 passed, 1 skipped (Windows test), 90.38% coverage
There was a problem hiding this comment.
Pull request overview
Upgrades the provider to github-copilot-sdk v0.2.0 (breaking API changes) and introduces a “Three‑Medium” architecture with SDK isolation, YAML-driven policy, and contract docs.
Changes:
- Replace direct SDK usage with an
sdk_adapter/membrane (quarantined imports, session lifecycle, event/tool extraction). - Introduce YAML configuration + markdown contracts to define provider behavior (streaming, errors, deny/destroy, SDK protection, observability).
- Restructure tests/fixtures to match updated SDK response shapes and new session creation semantics.
Reviewed changes
Copilot reviewed 69 out of 165 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/sdk_assumptions/test_deny_hook.py | Removes SDK behavioral assumption tests for deny-hook/tool execution. |
| tests/sdk_assumptions/conftest.py | Removes custom mocked SDK/session harness used by assumption tests. |
| tests/sdk_assumptions/init.py | Removes package docs for assumption test suite. |
| tests/sdk_assumptions/README.md | Removes assumption-suite upgrade workflow documentation. |
| tests/integration/init.py | Removes integration test package marker docstring. |
| tests/fixtures/sdk_responses.py | Adds realistic SDK response shape fixtures (Data wrapper, etc.). |
| tests/fixtures/config_capture.py | Adds strict mock client to capture create_session(**kwargs) config for SDK v0.2.0. |
| tests/fixtures/init.py | Exports new fixture/mocks surface for tests. |
| tests/init.py | Updates tests package/module docstring. |
| pyproject.toml | Bumps package to 2.0.0, upgrades SDK dependency to v0.2.x, adds pyright/ruff config, adjusts pytest config. |
| docs/ARCHITECTURE.md | Adds architecture overview documenting module structure and design decisions. |
| contracts/streaming-contract.md | Adds streaming behavior specification + anchors. |
| contracts/sdk-response.md | Adds SDK response extraction contract + anchors. |
| contracts/sdk-protection.md | Adds defensive invariants for tool capture + session management. |
| contracts/provider-protocol.md | Adds provider protocol specification + hooks/quality gates anchors. |
| contracts/observability.md | Adds observability spec and notes partial implementation. |
| contracts/event-vocabulary.md | Adds SDK→domain event vocabulary and config schema contract. |
| contracts/error-hierarchy.md | Adds SDK→kernel error translation contract and config schema. |
| contracts/deny-destroy.md | Adds deny+destroy non-negotiable pattern contract. |
| contracts/behaviors.md | Adds config-driven policy contract (retry/streaming/model selection, etc.). |
| amplifier_module_provider_github_copilot/tool_parsing.py | Adds tool call parsing to kernel ToolCall objects. |
| amplifier_module_provider_github_copilot/tool_capture.py | Removes legacy tool bridge/deny-hook creation module. |
| amplifier_module_provider_github_copilot/security_redaction.py | Adds secret redaction helpers for log hygiene. |
| amplifier_module_provider_github_copilot/sdk_adapter/types.py | Adds SDK boundary types + tool/image passthrough helpers. |
| amplifier_module_provider_github_copilot/sdk_adapter/tool_capture.py | Adds first-turn-only + deduplicated tool capture handler. |
| amplifier_module_provider_github_copilot/sdk_adapter/extract.py | Adds unified SDK event field extraction helper. |
| amplifier_module_provider_github_copilot/sdk_adapter/event_helpers.py | Adds SDK event type helpers + tool_request extraction. |
| amplifier_module_provider_github_copilot/sdk_adapter/client.py | Adds Copilot client wrapper using SubprocessConfig + v0.2 session config/hook model. |
| amplifier_module_provider_github_copilot/sdk_adapter/_spec_utils.py | Adds SDK-spec discovery via importlib.util.find_spec without importing SDK. |
| amplifier_module_provider_github_copilot/sdk_adapter/_imports.py | Adds SDK import quarantine module for SDK v0.2 concepts. |
| amplifier_module_provider_github_copilot/sdk_adapter/init.py | Exposes adapter “membrane” public surface. |
| amplifier_module_provider_github_copilot/request_adapter.py | Adds ChatRequest→CompletionRequest prompt/attachment adaptation. |
| amplifier_module_provider_github_copilot/observability.py | Adds YAML-driven observability event emission helpers + lifecycle context. |
| amplifier_module_provider_github_copilot/model_naming.py | Removes legacy model naming parsing/validation heuristics. |
| amplifier_module_provider_github_copilot/fake_tool_detection.py | Adds YAML-driven fake tool-call detection + retry logging helpers. |
| amplifier_module_provider_github_copilot/exceptions.py | Removes custom exception hierarchy in favor of kernel error types. |
| amplifier_module_provider_github_copilot/converters.py | Removes legacy message/response conversion utilities. |
| amplifier_module_provider_github_copilot/config/sdk_protection.yaml | Adds SDK protection policy for tool capture + session behavior. |
| amplifier_module_provider_github_copilot/config/retry.yaml | Adds retry + streaming timing policy values. |
| amplifier_module_provider_github_copilot/config/observability.yaml | Adds observability policy (event names, status, flags). |
| amplifier_module_provider_github_copilot/config/models.yaml | Adds provider identity + model catalog/defaults policy. |
| amplifier_module_provider_github_copilot/config/model_cache.yaml | Adds model cache policy. |
| amplifier_module_provider_github_copilot/config/fake-tool-detection.yaml | Adds fake tool-call detection policy. |
| amplifier_module_provider_github_copilot/config/events.yaml | Adds event classification policy mapping SDK event types to domain events. |
| amplifier_module_provider_github_copilot/config/errors.yaml | Adds error translation policy mappings. |
| amplifier_module_provider_github_copilot/config/init.py | Adds config package marker for importlib.resources. |
| amplifier_module_provider_github_copilot/completion.py | Adds new completion lifecycle implementation using v0.2 send/on + tool capture/abort. |
| amplifier_module_provider_github_copilot/_platform.py | Refactors binary discovery to use membrane spec discovery, caches platform facts. |
| amplifier_module_provider_github_copilot/_permissions.py | Updates execute-bit repair logic for SDK-bundled binaries (no world-exec). |
| README.md | Replaces token examples with placeholders. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Fix SDK version docstring: 0.1.32+ → 0.2.0+ (sdk_responses.py) - Fix assert_hook_registered docstring: clarify it checks session.on() - Fix unbounded event queue: use streaming.event_queue_size from YAML - Fix event handler race: always check idle event before dropping - Add live tests opt-in: '-m not live' default for CI (pyproject.toml) - Use default_factory=list: cleaner than lambda (types.py) - Use importlib.resources: consistent config loading (observability.py) - Align DENY_ALL with minimal reason strategy: 'Processing' + suppressOutput - Update tests to match new DENY_ALL structure Test results: Windows 858 passed, 2 skipped (Unix), 90.02% coverage
Add StreamingChatResponse class that extends ChatResponse with content_blocks for streaming UI compatibility. The loop-streaming orchestrator uses this to emit CONTENT_BLOCK_START/END events for real-time updates. Contract: streaming-contract:StreamingResponse:MUST:1-4 Changes: - Add StreamingChatResponse class with content_blocks and text fields - Update to_chat_response() to build both content (Pydantic) and content_blocks (dataclass) for dual-type system compliance - Add 8 TDD tests for StreamingChatResponse behavior - Update streaming-contract.md with new MUST constraints Test Results: - Windows: 866 passed, 2 skipped, 6 deselected | pyright: 0 errors - WSL: 873 passed, 1 skipped | Coverage: 90% (statement + branch) - Live tests: 6 passed (both platforms)
Add 25 integration tests to improve provider.py coverage from 74% to 95%. Tests exercise production code paths through MockCopilotClientWrapper. Coverage improvements: - Retry paths (success after failure, exception translation) - Fake tool detection (pattern match → correction retry) - TTFT warning (slow first token tracking) - Progressive streaming (text/thinking content emission) - Error events (dict, object, None data formats) - Tool capture + abort (timeout/exception handling) - Emit helpers (coordinator guard, task exception callback) - Close cleanup (pending emit task cancellation) - Queue full (bounded queue overflow handling) Add typings/ stubs for pyright compliance: - copilot SDK client stubs (CopilotClient, CopilotSession) - amplifier_core stubs (ModelInfo, ChatResponse, errors) Contract References: - behaviors:Retry:MUST:1-5 - streaming-contract:ProgressiveStreaming:SHOULD:1 - sdk-protection:Session:MUST:3,4
- MUST-FIX #1-4: SDK version, bounded queue, Makefile, docstring - SHOULD-FIX microsoft#5,7,8: event predicates, membrane bypass, empty args - NITS N1-N9: cleanup and doc corrections - Delete completion.py (duplicate streaming impl)
Stability: - Streaming accumulator lifecycle management - Model listing efficiency (single SDK call) - Content type detection accuracy - Error event sequencing - Null safety for SDK response chains Architecture: - Extract EventRouter for separation of concerns - Consolidate ConfigurationError via _compat.py - Move model fallbacks to config_loader.py (circular import fix) - Module-level imports for hot path optimization Quality Gates: - Windows: 947 passed, 2 skipped, 6 deselected - WSL: 948 passed, 1 skipped, 6 deselected - ruff: 0 errors - pyright: 0 errors Contract: streaming-contract, error-hierarchy, sdk-boundary
ARCHITECTURE.md: - Add _compat.py, event_router.py, _permissions.py, _platform.py - Add model_translation.py, tool_capture.py, event_helpers.py, extract.py - Add missing config files (sdk_protection.yaml, model_cache.yaml, fake-tool-detection.yaml) - Expand contracts table from 4 to 10 entries sdk-boundary.md (v1.2): - Add model_translation.py to membrane directory structure streaming-contract.md (v1.4): - Add EventRouter module reference - Update streaming flow diagram to show EventRouter
- Adopt safe_log_message() for exception logging to prevent credential leaks - Remove SKIP_SDK_CHECK escape hatch (fail-closed token security) - Reconcile deny-destroy contract with available_tools behavior - Strengthen weak contract tests with concrete assertions - Fix SubprocessConfig mocking in concurrent session tests - Resolve pyright type errors in test fixtures Test Statistics: Passed: 984 | Skipped: 1 | Warnings: 4 Coverage: 94% (statement + branch) Live tests: included
Swarm Review: PR #32 — SDK v0.2.0 UpgradeReviewed by 3 parallel agents (Anthropic/OpenAI/Gemini) each focused on a different angle: architecture, code correctness, and testing. Findings below are cross-validated against source. Critical — Fix Before Merge1. The function checks # Current (line 599-601):
if hasattr(response, "data"):
return extract_response_content(response.data, _depth + 1)
# .content check at line 604 is never reached if .data exists
# Fix: check .content first, or guard .data
if hasattr(response, "data") and response.data is not None:
return extract_response_content(response.data, _depth + 1)2.
Fix: Remove 3. Context extraction leaks unredacted secrets Context is extracted from the original (unredacted) message via # Current (line 456):
context = _extract_context(original_message, mapping.context_extraction)
# Fix: redact extracted context values
context = _extract_context(original_message, mapping.context_extraction)
context = {k: redact_sensitive_text(v) for k, v in context.items()}4. Python dataclass default is # Current (line 36):
log_response_text: bool = True
# Fix: match the secure YAML default
log_response_text: bool = FalseRecommended — Should Fix (follow-up OK)5. No end-to-end tool capture integration test 6. Membrane violation in 7. 8. Deleted test suites only partially replaced 9. Missing JWT pattern in Nice to Have — Non-blocking
Overall: solid SDK isolation architecture, strong test investment (860 tests, 90%+ coverage), clean provider protocol compliance. The 4 critical items are real bugs with straightforward fixes. |
Critical bugs fixed: - C1: Add null guard for response.data in streaming.py SDK can return None when recursing into tool_result fragments - C3: Apply redact_sensitive_text() to extracted context values Prevents credential leakage in error messages - C4: Change log_response_text default to False (secure default) Aligns Python mechanism with YAML policy per Three-Medium Improvements: - R5: Add E2E tool capture happy-path tests (TestE2EToolCaptureHappyPath) Covers complete flow from SDK event to accumulated buffer - R6: Fix membrane import in models.py Use sdk_adapter namespace, not internal model_translation module Test results: 980 passed, 94% coverage (Windows + WSL)
|
Samuel Lee (@samueljklee) — ALL findings addressed. Thanks for the rigorous swarm review — these were real bugs. Critical Findings
Recommended Findings
Note on C1The suggested Nice to Have — Response
Verification: |
Lines 1179-1373 contained agent conversation logs, not Python code. Valid tests preserved (lines 1-1178).
Samuel Lee (samueljklee)
left a comment
There was a problem hiding this comment.
Re-reviewed at 0664e2d. All 4 critical findings and 2 recommended findings from the swarm review are verified fixed:
| Finding | Status |
|---|---|
C1: .data before .content silent empty returns |
Fixed (streaming.py:602 — null guard added) |
C2: CAPIError double-mapping misclassification |
Fixed (errors.yaml:60 — CAPIError removed from ContextLengthError) |
| C3: Context extraction leaking unredacted secrets | Fixed (error_translation.py:459 — redaction applied to extracted context) |
C4: log_response_text default mismatch |
Fixed (fake_tool_detection.py:38 — default changed to False) |
| R5: E2E tool capture test missing | Fixed (test_provider_coverage.py:1021 — 3 test methods) |
| R6: Membrane violation import | Fixed (models.py:73 — imports via __init__.py) |
Bonus: JWT redaction pattern added (security_redaction.py:71).
Remaining open (follow-up OK, not blocking):
- R7: Inverted dependency in
sdk_adapter/client.py - R8: Deleted test suite coverage gaps (~2k net line loss in tests)
- Nice-to-haves: duplicate cache clears in conftest, double timeout, substring matching in
is_assistant_message, Three-Medium architecture discussion
## Summary Resolves all issues identified in internal code review, adds migration documentation for v1.x → v2.0.0 breaking changes. ## Issues Fixed (10 total) 1. **Security defaults** — log_tool_calls/log_correction_message now default to False (YAML is source of truth, Python matches) 2. **Silent data loss** — Correction path now raises on failure instead of returning empty response 3. **Finish reason case** — Normalized to lowercase (stop, tool_calls, length) 4. **mount() failure signal** — Now raises on failure (framework can detect) 5. **Null timestamp crash** — Model cache handles null JSON values 6. **Substring matching** — is_assistant_message uses explicit set matching 7. **Usage event filter** — Tool capture excludes usage events 8. **llm_lifecycle finally** — Error events emitted on exception 9. **close() await tasks** — Cancelled tasks are awaited before cleanup 10. **Correction path errors** — Now translated to kernel error types ## Migration Deliverables - MIGRATION.md: Complete v1.x → v2.0.0 migration guide - _deprecated.py: 17 symbols with helpful ImportError messages - __init__.py: __getattr__ hook for deprecation shims - README.md: SDK version updated to >=0.2.0,<0.3.0 ## Quality Gates | Gate | Result | |------|--------| | **Windows tests** | 980 passed, 2 skipped | | **WSL tests (live)** | 987 passed, 1 skipped | | **ruff check** | All checks passed | | **pyright** | 0 errors | | **Coverage** | 93% |
…Python dataclasses Follow-up to PR microsoft#32 review by @samueljklee: "retry.yaml (4 values), model_cache.yaml (1 value), sdk_protection.yaml (safety invariants) are over-engineered for a leaf provider module." Architecture change -- Two-Medium replaces Three-Medium: Python = policy + mechanism (config/_models.py, _policy.py, _sdk_protection.py) YAML = SDK-correlated tabular data only (config/data/errors.yaml, events.yaml) Collapsed 6 scalar YAML files to frozen Python dataclasses: config/retry.yaml -> RetryPolicy in config/_policy.py config/model_cache.yaml -> CacheConfig in config/_policy.py config/sdk_protection.yaml -> SdkProtectionPolicy in config/_sdk_protection.py config/fake-tool-detection.yaml -> LoggingConfig in config/_policy.py config/models.yaml -> ProviderIdentity in config/_models.py config/observability.yaml -> constants in observability.py Preserved as YAML (moved to config/data/ for importlib.resources): config/data/errors.yaml -- 14 SDK->kernel mappings (genuinely tabular) config/data/events.yaml -- 40+ event classifications (SDK-correlated) Test fixes: - 30+ test files: Three-Medium/models.yaml/retry.yaml refs -> Two-Medium - test_live_smoke.py: remove 2 permanently-skipping tests (send_and_wait and message_delta gated by org policy not available on this account) - test_sdk_assumptions.py: remove 2 dead SubprocessConfig tests (SubprocessConfig removed from SDK v0.2.x) - test_unified_error_config.py: fix stale path config/errors.yaml -> config/data/errors.yaml - conftest.py, test_cross_platform.py: pyright: ignore for Windows-only asyncio.WindowsSelectorEventLoopPolicy Repo hygiene: - .gitignore: align section order/style with microsoft/amplifier ecosystem - Makefile: --cov-branch, threshold 80->90%, add make live target - scripts/smoke_test.py: fix stale count, fix non-existent test_provider.py refs - scripts/amplifier_settings.yaml: remove (personal dev config leaked in) - README.md: fix stale RUN_LIVE_TESTS env var, fix tests/integration/ path - pyrightconfig.example.json: add contributor template Quality: Windows (Python 3.13): 1186 passed, 3 skipped, 97% branch coverage WSL (Python 3.14): 1188 passed, 1 skipped, 97% branch coverage ruff: 0 errors | pyright: 0 errors | all files >= 91% coverage
BREAKING CHANGE: Requires github-copilot-sdk>=0.2.0,<0.3.0
BREAKING CHANGE: CopilotClient now uses SubprocessConfig
BREAKING CHANGE: session.send() signature changed to send(prompt, attachments=...)
SDK v0.2.0 Upgrade:
New Architecture:
Test Results: