Skip to content

Handle tool and permission broadcasts via event model (protocol v3)#686

Merged
SteveSandersonMS merged 15 commits intomainfrom
stevesa/handle-broadcasts-for-permissions-and-tools
Mar 7, 2026
Merged

Handle tool and permission broadcasts via event model (protocol v3)#686
SteveSandersonMS merged 15 commits intomainfrom
stevesa/handle-broadcasts-for-permissions-and-tools

Conversation

@SteveSandersonMS
Copy link
Contributor

Overview

Replace the RPC callback pattern for external tool calls and permission requests with a broadcast event model. The runtime now broadcasts external_tool.requested and permission.requested as session events to all connected clients, and clients respond via session.tools.handlePendingToolCall and session.permissions.handlePendingPermissionRequest RPC methods.

Changes

Event-based handling (Node SDK):

  • Remove tool.call and permission.request RPC request handlers from client.ts
  • Add _handleBroadcastEvent in session.ts that intercepts broadcast request events, executes local handlers, and responds via the new RPC methods
  • Remove normalizeToolResult, buildUnsupportedToolResult, and related helper methods that are no longer needed

Codegen updates:

  • Regenerated rpc.ts and session-events.ts from updated runtime schemas
  • permission.completed event now includes result.kind enum for observing outcomes

Protocol version:

  • Bump SDK_PROTOCOL_VERSION from 2 to 3 to match the runtime breaking change

E2E tests (5 new multi-client tests):

  • Both clients see tool request and completion events
  • One client approves permission, both see the approved result
  • One client rejects permission, both see the denied result
  • Two clients register different tools (union semantics), agent uses both
  • Disconnecting client removes its tools while other client's tools persist

⚠️ E2E tests will fail until the corresponding runtime update is released

The runtime must include the broadcast model changes and per-connection tool tracking for these tests to pass. Until then, E2E tests that depend on the new protocol (multi-client tests, and any tests using protocol version 3) will fail.

@SteveSandersonMS SteveSandersonMS requested a review from a team as a code owner March 5, 2026 13:48
Copilot AI review requested due to automatic review settings March 5, 2026 13:48
@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

Cross-SDK Consistency Review

This PR introduces a breaking architectural change to the Node.js SDK by migrating from an RPC callback model to a broadcast event model for handling tool calls and permission requests. This change bumps the protocol version from 2 to 3.

🚨 Critical Inconsistency: Protocol Version Mismatch

Node.js SDK (this PR):

  • Protocol version: 3
  • Architecture: Broadcast event model (external_tool.requested / permission.requested events)
  • Clients respond via: session.tools.handlePendingToolCall / session.permissions.handlePendingPermissionRequest RPCs

Python, Go, and .NET SDKs (current state):

  • Protocol version: 2
  • Architecture: RPC callback model (tool.call / permission.request RPC requests)
  • Clients respond synchronously to the RPC request

Impact

This creates a breaking inconsistency across the SDK implementations:

  1. Protocol incompatibility: The Node.js SDK (v3) will not be compatible with the same runtime version as Python/Go/.NET SDKs (v2)
  2. Feature disparity: Only the Node.js SDK will support multi-client scenarios where multiple clients can observe and respond to tool/permission requests
  3. API breaking changes: The internal implementation has changed significantly (removal of handleToolCallRequest, handlePermissionRequest methods in client, addition of _handleBroadcastEvent in session)

Recommendation

To maintain cross-SDK consistency, all four SDKs should be updated together to implement the broadcast event model and bump to protocol version 3:

Required changes for Python SDK:

  • Update python/copilot/sdk_protocol_version.py: SDK_PROTOCOL_VERSION = 3
  • Remove RPC handlers: self._client.set_request_handler("tool.call", ...) and self._client.set_request_handler("permission.request", ...)
  • Add event handlers: Listen for external_tool.requested and permission.requested session events
  • Implement response methods: Add session.tools.handle_pending_tool_call() and session.permissions.handle_pending_permission_request() RPC calls

Required changes for Go SDK:

  • Update go/sdk_protocol_version.go: SdkProtocolVersion = 3
  • Remove RPC handlers: c.client.SetRequestHandler("tool.call", ...) and c.client.SetRequestHandler("permission.request", ...)
  • Add event handlers: Listen for external_tool.requested and permission.requested session events
  • Implement response methods: Add session.tools.HandlePendingToolCall() and session.permissions.HandlePendingPermissionRequest() RPC calls

Required changes for .NET SDK:

  • Update dotnet/src/SdkProtocolVersion.cs: private const int Version = 3;
  • Remove RPC handlers: rpc.AddLocalRpcMethod("tool.call", ...) and rpc.AddLocalRpcMethod("permission.request", ...)
  • Add event handlers: Listen for external_tool.requested and permission.requested session events
  • Implement response methods: Add session.Tools.HandlePendingToolCall() and session.Permissions.HandlePendingPermissionRequest() RPC calls

Alternative Approach

If updating all SDKs simultaneously is not feasible, consider:

  1. Keeping this PR in draft until all SDKs are ready
  2. Coordinating a synchronized release across all languages
  3. Documenting the temporary inconsistency and migration plan

Note

The PR description mentions: "⚠️ E2E tests will fail until the corresponding runtime update is released" — this confirms that this is a coordinated breaking change with the runtime. All SDK implementations should be updated together to maintain feature parity and avoid confusion for users who might want to use different language SDKs with the same runtime version.

Generated by SDK Consistency Review Agent for issue #686 ·

Copy link
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 updates the Node SDK to handle external tool calls and permission requests via broadcast session events (instead of per-connection RPC callbacks), aligns the Node client with a breaking runtime protocol change, and adds multi-client E2E coverage for the new behavior.

Changes:

  • Switch tool/permission handling to the broadcast event model by intercepting external_tool.requested and permission.requested in CopilotSession and responding via new session RPC methods.
  • Bump Node SDK_PROTOCOL_VERSION to 3 and regenerate protocol typings (rpc.ts, session-events.ts) to match updated runtime schemas.
  • Update/add E2E tests and snapshots, including new multi-client broadcast scenarios.

Reviewed changes

Copilot reviewed 16 out of 18 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
nodejs/src/client.ts Removes legacy tool.call / permission.request request handlers and relies on session event dispatch for tool/permission handling.
nodejs/src/session.ts Adds internal handling for broadcast request events and responds via new session RPC methods.
nodejs/src/sdkProtocolVersion.ts Bumps Node SDK protocol version from 2 → 3.
nodejs/src/generated/rpc.ts Adds new session RPC methods for responding to pending tool calls and permission requests; updates some schema fields.
nodejs/src/generated/session-events.ts Regenerates/expands session event types, including new external tool events and updated permission completion payload.
nodejs/test/e2e/harness/sdkTestContext.ts Adds useStdio option to allow TCP-mode tests needed for multi-client scenarios.
nodejs/test/e2e/multi-client.test.ts Adds new multi-client E2E tests validating broadcast semantics for tools and permissions.
test/snapshots/tools/*.yaml Updates tool-related snapshots to reflect new runtime/SDK behavior and outputs.
test/snapshots/multi_client/*.yaml Adds new snapshots for multi-client broadcast scenarios.
Comments suppressed due to low confidence (1)

nodejs/src/client.ts:1281

  • tool.call request handling (and the private handleToolCallRequest helper) was removed here, but there is still a unit test referencing handleToolCallRequest (see nodejs/test/client.test.ts). This will fail TypeScript compilation/tests until that test is updated or removed to reflect the new broadcast-event tool handling model.
        this.connection.onNotification("session.event", (notification: unknown) => {
            this.handleSessionEventNotification(notification);
        });

        this.connection.onNotification("session.lifecycle", (notification: unknown) => {
            this.handleSessionLifecycleNotification(notification);
        });

        // External tool calls and permission requests are now handled via broadcast events:
        // the server sends external_tool.requested / permission.requested as session event
        // notifications, and CopilotSession._dispatchEvent handles them internally by
        // executing the handler and responding via session.tools.respond / session.permissions.respond RPC.

@SteveSandersonMS SteveSandersonMS marked this pull request as draft March 5, 2026 18:19
@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

⚠️ Cross-SDK Consistency Review

This PR introduces a breaking protocol change (version 2 → 3) and implements a new broadcast event model for tool and permission handling in the Node.js SDK only. This creates a significant consistency gap with the other SDK implementations.

Current State After This PR

SDK Protocol Version Tool/Permission Pattern Multi-Client Support
Node.js 3 Broadcast events Yes
Python 2 ❌ RPC callbacks ❌ No ❌
Go 2 ❌ RPC callbacks ❌ No ❌
.NET 2 ❌ RPC callbacks ❌ No ❌

Key Inconsistencies

1. Protocol Version Mismatch

The Node SDK bumps to protocol version 3, while Python, Go, and .NET remain on version 2. This means:

  • Node SDK clients will only work with runtime version 3+
  • Other SDKs will only work with runtime version 2
  • Users cannot mix SDK languages in multi-client scenarios

Files affected:

  • nodejs/src/sdkProtocolVersion.ts - bumped to 3
  • python/copilot/sdk_protocol_version.py - still 2
  • go/sdk_protocol_version.go - still 2
  • dotnet/src/SdkProtocolVersion.cs - still 2

2. Tool & Permission Handling Pattern

Node SDK now uses broadcast events (external_tool.requested, permission.requested) and responds via new RPC methods, while other SDKs still use the old callback pattern:

Node.js (new pattern):

  • Listen for external_tool.requested / permission.requested session events
  • Respond via session.tools.handlePendingToolCall / session.permissions.handlePendingPermissionRequest RPC

Python/Go/.NET (old pattern):

  • Still register tool.call / permission.request RPC request handlers
  • Respond directly to the RPC request

Files that need updates:

  • python/copilot/client.py:1351-1352 - still sets tool.call and permission.request handlers
  • go/client.go:1289-1290 - still sets tool.call and permission.request handlers
  • dotnet/src/Client.cs:1125-1126 - still sets tool.call and permission.request handlers

3. Multi-Client Support

The Node SDK now supports multiple clients connecting to the same session (all receive broadcast events). This capability doesn't exist in other SDKs and would require:

  • Event handling for broadcast request events
  • Coordination of responses from multiple clients
  • E2E tests for multi-client scenarios

Files that demonstrate this:

  • nodejs/test/e2e/multi-client.test.ts - 5 new multi-client test cases
  • ❌ Python, Go, .NET - no multi-client tests or support

4. New Extension Export

Node SDK adds extension.ts export for child process scenarios - unclear if this is needed in other SDKs.

Recommendations

To maintain cross-SDK consistency, the following options should be considered:

Option A: Update all SDKs in parallel

  • Port the broadcast event model to Python, Go, and .NET
  • Bump all SDKs to protocol version 3 simultaneously
  • Add multi-client support and tests across all languages
  • Ensures feature parity but requires significant coordination

Option B: Staged rollout with version compatibility

  • Document that Node SDK v3 requires runtime v3+
  • Plan follow-up PRs to port changes to other SDKs
  • Track the migration in a GitHub issue
  • Temporary inconsistency is acceptable if tracked and planned

Option C: Keep protocol version 2 support in Node SDK

  • Make the Node SDK backward compatible with both v2 and v3 protocols
  • Allow gradual migration of other SDKs
  • More complex but maintains broader compatibility

Questions for Maintainers

  1. Is there a timeline for porting these changes to Python, Go, and .NET?
  2. Should protocol version 3 support be tracked as a feature gap in other SDKs?
  3. Are there any runtime compatibility considerations that affect the rollout strategy?
  4. Should this PR be labeled as breaking change / requires coordination across SDKs?

Note: This is a technical consistency review, not a judgment on code quality. The Node SDK implementation looks well-designed - the concern is ensuring the other SDKs evolve in parallel to maintain API parity across languages.

Generated by SDK Consistency Review Agent for issue #686 ·

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Generated by SDK Consistency Review Agent for issue #686

* This must match the version expected by the copilot-agent-runtime server.
*/
export const SDK_PROTOCOL_VERSION = 2;
export const SDK_PROTOCOL_VERSION = 3;
Copy link
Contributor

Choose a reason for hiding this comment

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

Cross-SDK Consistency: This protocol version bump to 3 is not reflected in the other SDKs:

  • Python: python/copilot/sdk_protocol_version.py - still at version 2
  • Go: go/sdk_protocol_version.go - still at version 2
  • .NET: dotnet/src/SdkProtocolVersion.cs - still at version 2

This creates a compatibility issue where Node SDK clients will only work with runtime v3+, while other SDK clients remain on v2. Consider coordinating this version bump across all SDKs or documenting the migration plan.

@@ -0,0 +1,289 @@
/*---------------------------------------------------------------------------------------------
Copy link
Contributor

Choose a reason for hiding this comment

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

Cross-SDK Consistency: These multi-client E2E tests demonstrate new functionality that doesn't exist in the other SDKs. For feature parity, similar tests should be added to:

  • python/e2e/ - no multi-client tests currently
  • go/ - no multi-client tests currently
  • dotnet/test/ - no multi-client tests currently

The multi-client capability requires the broadcast event model, so these tests can't be ported until the other SDKs implement the same pattern.

* rejections, consistent with standard EventEmitter / event handler semantics.
* @internal
*/
private _handleBroadcastEvent(event: SessionEvent): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

Cross-SDK Consistency: This new broadcast event handling pattern for external_tool.requested and permission.requested is Node-only. The other SDKs still use the old RPC callback pattern:

  • Python: client.py:1351-1352 - still registers tool.call and permission.request RPC handlers
  • Go: client.go:1289-1290 - same old pattern
  • .NET: Client.cs:1125-1126 - same old pattern

For consistency, these SDKs would need similar _handleBroadcastEvent implementations that:

  1. Listen for broadcast session events instead of RPC requests
  2. Execute handlers locally
  3. Respond via the new RPC methods (session.tools.handlePendingToolCall, session.permissions.handlePendingPermissionRequest)


import { CopilotClient } from "./client.js";

export const extension = new CopilotClient({ isChildProcess: true });
Copy link
Contributor

Choose a reason for hiding this comment

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

Cross-SDK Consistency: This new extension export for child process scenarios is Node-only.

Questions:

  1. Is this functionality needed in other SDKs?
  2. Should Python, Go, and .NET provide equivalent child process integration patterns?
  3. Is this a Node-specific use case or a general SDK feature?

If this is generally useful, consider adding equivalent exports in the other SDKs.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

Cross-SDK Consistency Review

This PR introduces a significant architectural change to the Node.js SDK, replacing the RPC callback pattern for tool calls and permission requests with a broadcast event model. This is a breaking change (protocol version bumped from 2 → 3) that enables multi-client scenarios where multiple SDK clients can connect to the same CLI session.

Summary of Changes (Node.js SDK)

Before (Protocol v2): The runtime invoked tool.call and permission.request as RPC requests directly to a single client, which responded synchronously.

After (Protocol v3): The runtime broadcasts external_tool.requested and permission.requested as session events to all connected clients. Each client with a matching handler executes it locally and responds via new RPC methods:

  • session.tools.handlePendingToolCall
  • session.permissions.handlePendingPermissionRequest

This enables:

  • Multi-client tool registration (union semantics - each client can register different tools)
  • Multi-client permission handling (first response wins)
  • Broadcast event visibility (all clients see tool requests and completions)

⚠️ Cross-SDK Consistency Gap

The Python, Go, and .NET SDKs still use the old protocol v2 pattern:

Python (python/copilot/client.py):

# Line 1351-1352
self._client.set_request_handler("tool.call", self._handle_tool_call_request)
self._client.set_request_handler("permission.request", self._handle_permission_request)

Go (go/client.go):

// Line 1289-1290
c.client.SetRequestHandler("tool.call", jsonrpc2.RequestHandlerFor(c.handleToolCallRequest))
c.client.SetRequestHandler("permission.request", jsonrpc2.RequestHandlerFor(c.handlePermissionRequest))

.NET (dotnet/src/Client.cs):

// Line 1125-1126
rpc.AddLocalRpcMethod("tool.call", handler.OnToolCall);
rpc.AddLocalRpcMethod("permission.request", handler.OnPermissionRequest);

All three SDKs also still have SDK_PROTOCOL_VERSION = 2.

Recommendations

To maintain cross-SDK consistency and enable the same multi-client capabilities across all languages:

  1. Update Python, Go, and .NET to implement the same broadcast event model:

    • Remove tool.call and permission.request RPC request handlers
    • Listen for external_tool.requested and permission.requested session events
    • Respond via session.tools.handlePendingToolCall and session.permissions.handlePendingPermissionRequest RPC calls
    • Bump SDK_PROTOCOL_VERSION to 3 in all SDKs
  2. Add equivalent E2E tests for multi-client scenarios in each SDK (similar to nodejs/test/e2e/multi-client.test.ts)

  3. Update SDK documentation to explain:

    • The broadcast model and multi-client capabilities
    • Breaking changes from protocol v2 → v3
    • Migration guide for existing applications

Timeline Considerations

The PR description notes that "E2E tests will fail until the corresponding runtime update is released". This suggests:

  • The Node.js SDK changes can merge once the runtime is updated
  • Python, Go, and .NET should be updated in lockstep to avoid a prolonged period where only Node.js supports protocol v3

Alternative: Phased Rollout

If immediate parity isn't feasible, consider:

  • Documenting this as a Node.js-only feature (temporarily)
  • Creating tracking issues for Python (#XXX), Go (#XXX), and .NET (#XXX) implementations
  • Adding a section to the main README noting feature parity status

Verdict: This is an excellent architectural improvement that enables powerful multi-client scenarios. However, it creates a significant consistency gap across SDKs. I recommend planning the rollout to other languages before or shortly after merging this PR to maintain the SDK's cross-language feature parity.

Generated by SDK Consistency Review Agent for issue #686 ·

@github-actions
Copy link
Contributor

github-actions bot commented Mar 6, 2026

Cross-SDK Consistency Review

This PR introduces a significant architectural change that currently only affects the Node.js SDK. The other three SDK implementations (Python, Go, and .NET) still use the old RPC callback pattern and will need similar updates to maintain feature parity.

Summary of Changes

Architecture Change:

  • Before: Runtime sends tool.call and permission.request RPC requests, SDK responds synchronously
  • After: Runtime broadcasts external_tool.requested and permission.requested events, SDK listens and responds via new RPC methods (session.tools.handlePendingToolCall and session.permissions.handlePendingPermissionRequest)

Protocol Version: Bumped from 2 → 3 (breaking change)

Cross-SDK Impact

✅ Node.js/TypeScript (this PR)

  • Replaced RPC handlers in client.ts with broadcast event handling in session.ts
  • Added _handleBroadcastEvent() method to intercept and handle tool/permission events
  • Updated to protocol version 3

❌ Python (python/copilot/)

  • Still using RPC callback pattern in client.py:1441-1474 (_handle_permission_request)
  • Still using RPC callback pattern in client.py:1531-1580 (_handle_tool_call_request)
  • Protocol version: still at 2
  • Needs update to match Node implementation

❌ Go (go/)

  • Still using RPC handlers in client.go:1289-1290 (handleToolCallRequest, handlePermissionRequest)
  • Protocol version: still at 2
  • Needs update to match Node implementation

❌ .NET (dotnet/src/)

  • Similar RPC handler pattern (confirmed via grep)
  • Protocol version: still at 2
  • Needs update to match Node implementation

Recommendations

  1. Create follow-up issues/PRs to implement the broadcast event model in Python, Go, and .NET SDKs
  2. Consider blocking this merge until all SDKs are updated together, OR
  3. Document the protocol version mismatch and create a migration plan with target dates for other SDKs
  4. Update documentation to explain the protocol version compatibility matrix

Multi-Client Test Coverage

The PR adds excellent E2E tests for multi-client scenarios (both clients seeing events, tool registration, etc.), which is great! However, these tests currently only cover the Node SDK. When implementing the broadcast model in other SDKs, similar test coverage should be added.


Note: This is not blocking the PR itself (which looks well-implemented), but flagging the cross-SDK consistency gap that will need to be addressed to maintain feature parity across all four language implementations.

Generated by SDK Consistency Review Agent for issue #686 ·

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Generated by SDK Consistency Review Agent for issue #686

* This must match the version expected by the copilot-agent-runtime server.
*/
export const SDK_PROTOCOL_VERSION = 2;
export const SDK_PROTOCOL_VERSION = 3;
Copy link
Contributor

Choose a reason for hiding this comment

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

Cross-SDK consistency note: The protocol version files in other SDKs still show version 2:

  • python/copilot/sdk_protocol_version.py:9 - SDK_PROTOCOL_VERSION = 2
  • go/sdk_protocol_version.go:7 - const SdkProtocolVersion = 2
  • dotnet/src/SdkProtocolVersion.cs:14 - private const int Version = 2

These will need to be updated when the broadcast event model is implemented in those SDKs. (Note: These files are auto-generated by update-protocol-version.ts, so the generator script may need updates too.)

* rejections, consistent with standard EventEmitter / event handler semantics.
* @internal
*/
private _handleBroadcastEvent(event: SessionEvent): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

Cross-SDK consistency note: This broadcast event handling pattern (external_tool.requested and permission.requested) needs to be implemented in the other SDKs:

Python equivalent location: python/copilot/session.py - Add similar event interception in the event dispatch logic
Go equivalent location: go/session.go - Add broadcast event handling
.NET equivalent location: dotnet/src/Session.cs - Add broadcast event handling

Currently, Python uses client.py:1531-1580 for _handle_tool_call_request (RPC callback pattern), Go uses client.go:1309 for handleToolCallRequest, and .NET has similar RPC handler patterns that need to be replaced with this event-based approach.

} else if (typeof rawResult === "string") {
result = rawResult;
} else {
result = JSON.stringify(rawResult);
Copy link
Contributor

Choose a reason for hiding this comment

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

Cross-SDK consistency note: The new RPC methods session.tools.handlePendingToolCall will need to be added to the generated RPC clients in other SDKs:

  • Python: Generated RPC types (likely in a similar location to nodejs/src/generated/rpc.ts)
  • Go: Generated RPC client
  • .NET: Generated RPC client

This appears to be part of the codegen from runtime schemas, so it may be automatically generated once the other SDKs regenerate their types from the updated schemas.

}

if (options.isChildProcess && (options.cliUrl || options.useStdio === false)) {
throw new Error(
Copy link
Contributor

Choose a reason for hiding this comment

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

Cross-SDK consistency note: These RPC request handler removals from client.ts have equivalents in other SDKs that will need similar cleanup:

  • Python client.py:1351-1352:

    self._client.set_request_handler("tool.call", self._handle_tool_call_request)
    self._client.set_request_handler("permission.request", self._handle_permission_request)
  • Go client.go:1289-1290:

    c.client.SetRequestHandler("tool.call", jsonrpc2.RequestHandlerFor(c.handleToolCallRequest))
    c.client.SetRequestHandler("permission.request", jsonrpc2.RequestHandlerFor(c.handlePermissionRequest))
  • .NET: Similar handler registration that needs to be removed

These handlers should be removed and replaced with the broadcast event pattern.

@@ -0,0 +1,289 @@
/*---------------------------------------------------------------------------------------------
Copy link
Contributor

Choose a reason for hiding this comment

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

Cross-SDK consistency note: Excellent multi-client test coverage! When implementing the broadcast event model in Python, Go, and .NET, similar E2E tests should be added:

  • Both clients seeing tool request/completion events
  • Permission approval/rejection scenarios with multiple clients
  • Tool union semantics (multiple clients with different tools)
  • Tool persistence when clients disconnect

These test scenarios will help validate that the broadcast model works correctly across all SDK implementations.

knifeyspoony pushed a commit to knifeyspoony/copilot-sdk-rust that referenced this pull request Mar 6, 2026
Change verify_protocol_version() from strict equality to >= check.
The Copilot CLI v0.0.423 reports protocol version 3, but the v2 RPC
callback pattern (tool.call, permission.request) is still supported.
Protocol v3 adds broadcast events for multi-client scenarios
(github/copilot-sdk#686) which we don't need yet.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Contributor

github-actions bot commented Mar 7, 2026

Cross-SDK Consistency Review

This PR implements a significant architectural change in the Node.js SDK only — replacing the RPC callback pattern for tool calls and permission requests with a broadcast event model. This creates a major inconsistency across the SDK implementations.

What Changed in Node.js

The Node.js SDK now:

  1. Removes tool.call and permission.request RPC request handlers from client.ts
  2. Adds _handleBroadcastEvent in session.ts that intercepts external_tool.requested and permission.requested events
  3. Responds via new RPC methods: session.tools.handlePendingToolCall and session.permissions.handlePendingPermissionRequest
  4. Bumps SDK_PROTOCOL_VERSION from 2 to 3

Current State in Other SDKs

Python, Go, and .NET still use the old RPC callback pattern:

  • Python (client.py lines 1351-1352, 1432-1433): self._client.set_request_handler("tool.call", ...) and self._client.set_request_handler("permission.request", ...)
  • Go (client.go lines 1289-1290): c.client.SetRequestHandler("tool.call", ...) and c.client.SetRequestHandler("permission.request", ...)
  • .NET (Client.cs lines 1125-1126): rpc.AddLocalRpcMethod("tool.call", ...) and rpc.AddLocalRpcMethod("permission.request", ...)

Protocol Version Mismatch

  • Node.js: SDK_PROTOCOL_VERSION = 3 (after this PR)
  • Python: SDK_PROTOCOL_VERSION = 2
  • Go: SdkProtocolVersion = 2
  • .NET: Version = 2

Generated Code is Ready

✅ All SDKs already have the generated types for:

  • external_tool.requested and permission.requested events
  • handlePendingToolCall/HandlePendingToolCall/handle_pending_tool_call RPC methods
  • handlePendingPermissionRequest/HandlePendingPermissionRequest/handle_pending_permission_request RPC methods

Recommendations

To maintain cross-SDK consistency, the Python, Go, and .NET SDKs need equivalent updates:

  1. Implement broadcast event handling in session.py, session.go, and Session.cs similar to Node.js _handleBroadcastEvent
  2. Remove old RPC request handlers (tool.call, permission.request) from client initialization
  3. Bump protocol version to 3 in all SDKs (or coordinate a synchronized release)
  4. Add E2E tests for multi-client scenarios (like the 5 new tests in nodejs/test/e2e/multi-client.test.ts)

This is a breaking protocol change that should ideally be coordinated across all language implementations to maintain feature parity and prevent version fragmentation.

Generated by SDK Consistency Review Agent for issue #686 ·

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Generated by SDK Consistency Review Agent for issue #686

ToolResult,
ToolResultObject,
TypedSessionLifecycleHandler,
} from "./types.js";
Copy link
Contributor

Choose a reason for hiding this comment

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

Good: Removing the old RPC callback types. However, Python, Go, and .NET still reference and use these patterns in their client implementations:

  • Python client.py: Uses _handle_tool_call_request and _handle_permission_request (lines 1351-1352, 1432-1433)
  • Go client.go: Uses handleToolCallRequest and handlePermissionRequest (lines 1289-1290)
  • .NET Client.cs: Uses OnToolCall and OnPermissionRequest (lines 1125-1126)

These will need to be removed in coordination with implementing the broadcast event model.

* rejections, consistent with standard EventEmitter / event handler semantics.
* @internal
*/
private _handleBroadcastEvent(event: SessionEvent): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing in other SDKs: This new _handleBroadcastEvent implementation is Node.js-specific.

Python, Go, and .NET need equivalent implementations to:

  • Listen for external_tool.requested and permission.requested events in their _dispatch_event/dispatchEvent/DispatchEvent methods
  • Execute local tool/permission handlers
  • Respond via handle_pending_tool_call/HandlePendingToolCall and handle_pending_permission_request/HandlePendingPermissionRequest RPC methods

The generated types are already present in all SDKs, so the implementation should be straightforward.

* This must match the version expected by the copilot-agent-runtime server.
*/
export const SDK_PROTOCOL_VERSION = 2;
export const SDK_PROTOCOL_VERSION = 3;
Copy link
Contributor

Choose a reason for hiding this comment

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

Protocol version inconsistency: This bumps the Node.js SDK to version 3, but Python, Go, and .NET SDKs remain at version 2. This will cause protocol version mismatch errors when different SDK clients try to connect to the same runtime.

Consider coordinating this version bump across all SDKs, or use the protocol version range negotiation to allow v2/v3 coexistence during a transition period.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 7, 2026

Cross-SDK Consistency Review: Protocol Version & Event Handling

This PR implements a significant architectural change to tool and permission handling, but only for the Node.js SDK. This creates a protocol version mismatch and feature gap across the SDK implementations.

📋 Summary of Changes (Node.js SDK)

Protocol Evolution:

  • Protocol version bumped from v2 → v3
  • Tool/permission handling changed from RPC callbacks to broadcast events

Old pattern (v2):

// Runtime calls SDK directly via RPC
RPC: tool.call  SDK handler  Response
RPC: permission.request  SDK handler  Response

New pattern (v3):

// Runtime broadcasts events, SDK responds via RPC
Event: external_tool.requested  SDK handler  RPC: session.tools.handlePendingToolCall
Event: permission.requested  SDK handler  RPC: session.permissions.handlePendingPermissionRequest

🚨 Consistency Issues Identified

1. Protocol Version Mismatch

  • Node.js: SDK_PROTOCOL_VERSION = 3 (this PR)
  • Python: SDK_PROTOCOL_VERSION = 2 (unchanged)
  • Go: SdkProtocolVersion = 2 (unchanged)
  • .NET: Version = 2 (unchanged)

2. Missing Broadcast Event Handling Implementation

The new _handleBroadcastEvent() pattern exists only in Node.js:

Node.js (this PR):

// nodejs/src/session.ts
private _handleBroadcastEvent(event: SessionEvent): void {
    if (event.type === "external_tool.requested") {
        // Execute handler and respond via RPC
        await this.rpc.tools.handlePendingToolCall({...})
    }
    // ...
}

Python (still v2 pattern):

# python/copilot/client.py
self._client.set_request_handler("tool.call", self._handle_tool_call_request)
# No broadcast event handling ❌

Go (still v2 pattern):

// go/client.go
c.client.SetRequestHandler("tool.call", jsonrpc2.RequestHandlerFor(c.handleToolCallRequest))
// No broadcast event handling ❌

.NET (still v2 pattern):

// dotnet/src/Client.cs
rpc.AddLocalRpcMethod("tool.call", handler.OnToolCall);
// No broadcast event handling ❌

📦 What's Updated vs. What's Missing

✅ Updated (all SDKs):

  • Generated RPC type definitions (session.tools.handlePendingToolCall, etc.)
  • Generated session event types (external_tool.requested, permission.requested)

❌ Missing (Python, Go, .NET):

  • Implementation of broadcast event listeners
  • Implementation of RPC response methods
  • Protocol version bump to v3
  • Removal of old tool.call / permission.request RPC handlers

💡 Recommendation

The new multi-client E2E tests in this PR demonstrate scenarios where multiple SDK clients interact with the same session. However, those tests will only work correctly if all SDK clients use the same protocol version and event handling model.

Options:

  1. Implement for all SDKs ✅ (Recommended for consistency)

    • Update Python, Go, and .NET to implement the same broadcast event pattern
    • Bump all SDK protocol versions to v3
    • Remove old RPC request handlers from all SDKs
    • Ensures feature parity and multi-language compatibility
  2. Document the gap ⚠️ (If intentional phased rollout)

    • Clearly document that v3 is Node-only for now
    • Note that multi-client scenarios mixing v2/v3 SDKs may not work correctly
    • Create tracking issues for Python/Go/.NET updates
  3. Version negotiation 🔄 (For backward compatibility)

    • Consider supporting both v2 and v3 protocols within the same SDK
    • Use protocol version to determine which pattern to use
    • Allows gradual migration

📝 Additional Notes

  • The PR description notes that "E2E tests will fail until the corresponding runtime update is released" — this same caveat applies to other language SDKs
  • The generated code changes are present for all languages, but without the implementation changes, those types are unused
  • The broadcast model enables multi-client scenarios (which the new tests demonstrate), but only if all clients speak the same protocol version

Question for the PR author: Is the plan to update Python, Go, and .NET SDKs in follow-up PRs, or is this intended to be a Node.js-specific change? If the former, it would be helpful to have tracking issues linked in the PR description.

Generated by SDK Consistency Review Agent for issue #686 ·

SteveSandersonMS and others added 12 commits March 7, 2026 10:29
…callbacks

Update the Node SDK to listen for broadcast events (external_tool.requested,
permission.requested) and respond via sharedApi RPC methods
(session.tools.handlePendingToolCall, session.permissions.handlePendingPermissionRequest)
instead of the old tool.call/permission.request RPC handlers.

- Regenerate rpc.ts and session-events.ts from runtime schemas
- Add _handleBroadcastEvent to session.ts for event interception
- Remove old RPC handlers from client.ts
- Add useStdio option to test harness for TCP mode

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Verify that when two SDK clients are connected to the same CLI process,
both see external_tool.requested/completed and permission.requested/completed
events. The permission test has client 1 manually approve while client 2
observes, confirming the broadcast model works across connections.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ent 1 approves

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rtions

Add E2E test verifying one client can reject a permission and both clients
see the denial in the permission.completed event. Regenerate SDK types from
updated runtime schemas so permission.completed includes result.kind enum.
Use type-narrowing filters instead of casts in test assertions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…leanup

- two clients register different tools and agent uses both: client1 has
  city_lookup, client2 has currency_lookup, verifies union semantics
- disconnecting client removes its tools: client2's ephemeral_tool removed
  after disconnect, client1's stable_tool persists
- Update existing test: client2 resumes with NO tools (doesn't overwrite)
- Regenerated snapshots for existing tests (minor wording changes)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Bump SDK_PROTOCOL_VERSION from 2 to 3 to match runtime breaking change
- Wrap fallback RPC calls in _executeToolAndRespond and
  _executePermissionAndRespond with try/catch to prevent unhandled
  rejections if the connection is disposed mid-flight

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These are no different from any other event handler — let rejections
propagate naturally.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove unused session2 variable in multi-client test
- Document fire-and-forget semantics in _handleBroadcastEvent
- Fix null/undefined handling in tool result serialization
- Fix stale RPC method names in comment

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SteveSandersonMS SteveSandersonMS force-pushed the stevesa/handle-broadcasts-for-permissions-and-tools branch from 34a8357 to 965b84b Compare March 7, 2026 10:31
@github-actions
Copy link
Contributor

github-actions bot commented Mar 7, 2026

Cross-SDK Consistency Review

This PR introduces a breaking change that migrates from RPC callbacks to a broadcast event model for tool calls and permission requests. However, the implementation is incomplete across SDKs, creating a significant feature parity gap.

✅ Fully Implemented (Protocol v3)

  • Node.js SDK: Complete implementation with broadcast event handling, new RPC response methods, and multi-client E2E tests
  • Go SDK: Complete implementation with broadcast event handling, new RPC response methods, and multi-client E2E tests

❌ Missing Implementation (Still on Protocol v2)

  • Python SDK: Only has generated types and RPC methods, but:

    • Still uses old tool.call and permission.request RPC handlers (client.py:1357-1358, 1438-1439)
    • Missing _handle_broadcast_event logic in session
    • Missing _execute_tool_and_respond / _execute_permission_and_respond methods
    • Protocol version still at 2 (sdk_protocol_version.py:9)
    • No multi-client E2E tests
  • .NET SDK: Only has generated types and RPC methods, but:

    • Still uses old tool.call and permission.request RPC handlers (Client.cs:1132-1133)
    • Missing broadcast event handling in Session class
    • Missing execute and respond helper methods
    • Protocol version still at 2 (SdkProtocolVersion.cs:14)
    • No multi-client E2E tests

Required Changes for Python & .NET

To bring Python and .NET into feature parity, they need:

  1. Protocol version bump from 2 to 3
  2. Remove old RPC handlers: Remove tool.call and permission.request request handlers from client setup
  3. Add broadcast event handling: Implement equivalent of Node's _handleBroadcastEvent that:
    • Intercepts external_tool.requested events
    • Intercepts permission.requested events
    • Executes local handlers
    • Responds via session.tools.handlePendingToolCall / session.permissions.handlePendingPermissionRequest RPCs
  4. Add helper methods: Implement executeToolAndRespond and executePermissionAndRespond (or equivalent names following language conventions)
  5. Add E2E tests: Multi-client tests covering the same scenarios as Node.js and Go

Impact

Until Python and .NET are updated:

  • They will be incompatible with runtime version 3
  • Multi-client scenarios won't work correctly
  • Tool and permission handling will fail when the runtime broadcasts events instead of making direct RPC calls
  • The SDKs will have divergent behavior, making it harder for developers to switch between languages

Recommendation

Either:

  1. Complete the migration for all four SDKs in this PR before merging, OR
  2. Split this into language-specific PRs with clear coordination to ensure all SDKs are updated before the runtime ships protocol v3, OR
  3. Add backward compatibility to handle both protocol v2 (RPC callbacks) and v3 (broadcast events) until all SDKs are migrated

The current state leaves Python and .NET users with a broken SDK when used with the new runtime.

Generated by SDK Consistency Review Agent for issue #686 ·

@github-actions
Copy link
Contributor

github-actions bot commented Mar 7, 2026

⚠️ Cross-SDK Consistency Review

This PR updates the Node.js and Go SDKs to use the new protocol v3 broadcast event model for handling tool calls and permission requests. However, Python and .NET SDKs have not been updated, creating a significant cross-language inconsistency.

Summary of Changes (Node.js & Go)

Protocol version bumped to 3

  • Node: nodejs/src/sdkProtocolVersion.tsSDK_PROTOCOL_VERSION = 3
  • Go: go/sdk_protocol_version.goSdkProtocolVersion = 3

Removed old RPC callback handlers

  • Removed tool.call and permission.request RPC request handlers from client

Added broadcast event handling

  • New _handleBroadcastEvent / handleBroadcastEvent methods in session
  • Listen for external_tool.requested and permission.requested session events
  • Respond via new RPC methods: session.tools.handlePendingToolCall and session.permissions.handlePendingPermissionRequest

Multi-client E2E tests added

  • 5 new tests validating the broadcast model with multiple connected clients

❌ Inconsistencies with Python & .NET

1. Protocol Version Mismatch

  • Python: Still on version 2 (python/copilot/sdk_protocol_version.py:9)
  • .NET: Still on version 2 (dotnet/src/SdkProtocolVersion.cs:14)

2. Still Using Old RPC Callback Pattern

Python (python/copilot/client.py):

# Lines 1357-1358
self._client.set_request_handler("tool.call", self._handle_tool_call_request)
self._client.set_request_handler("permission.request", self._handle_permission_request)

.NET (dotnet/src/Client.cs):

// Lines 1132-1133
rpc.AddLocalRpcMethod("tool.call", handler.OnToolCall);
rpc.AddLocalRpcMethod("permission.request", handler.OnPermissionRequest);

3. Missing Broadcast Event Handling

Neither Python nor .NET have implemented the equivalent of _handleBroadcastEvent to:

  • Listen for external_tool.requested / permission.requested events
  • Execute local handlers and respond via the new RPC methods

4. Generated Code Exists But Not Used

Both Python and .NET have the generated RPC method definitions for:

  • handle_pending_tool_call / HandlePendingToolCallAsync
  • handle_pending_permission_request / HandlePendingPermissionRequestAsync
  • Event types EXTERNAL_TOOL_REQUESTED / ExternalToolRequestedEvent
  • Event types PERMISSION_REQUESTED / PermissionRequestedEvent

But these are not being used in the actual session/client implementation.

5. No Multi-Client Tests

Python and .NET lack the multi-client E2E tests that validate the broadcast model.


🔧 Recommended Actions

Option 1: Complete the migration (preferred)
Update Python and .NET SDKs to match the Node/Go implementation:

  1. Bump protocol version to 3
  2. Remove old tool.call and permission.request RPC handlers from client
  3. Add handle_broadcast_event / HandleBroadcastEvent method in session
  4. Use the generated RPC methods to respond to broadcast events
  5. Add multi-client E2E tests

Option 2: Document the gap
If Python/.NET updates are planned for a follow-up PR:

  • Add a comment or issue linking to track the Python/.NET migration
  • Update the PR description to clarify this is a phased rollout

Option 3: Feature flag / version negotiation
If both models need to coexist temporarily:

  • Consider protocol version negotiation that falls back to v2 for Python/.NET clients
  • Ensure runtime supports both patterns during transition

📝 Additional Notes

  • The generated files have already been updated (RPC schemas, session events), so the foundation is in place
  • The core implementation pattern is identical between Node/Go, making it straightforward to replicate in Python/.NET
  • This is a breaking protocol change that affects multi-language deployments where different SDK versions interact with the same runtime

Would you like me to:

  1. Open issues to track the Python and .NET updates?
  2. Help identify the specific code changes needed for those SDKs?
  3. Draft the equivalent implementation for Python or .NET?

Generated by SDK Consistency Review Agent for issue #686 ·

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Generated by SDK Consistency Review Agent for issue #686

@@ -0,0 +1,294 @@
/*---------------------------------------------------------------------------------------------
Copy link
Contributor

Choose a reason for hiding this comment

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

Cross-SDK consistency: This new multi-client E2E test file validates the broadcast event model with multiple connected clients (5 test scenarios). Similar tests exist in Go (go/internal/e2e/multi_client_test.go).

Missing in Python and .NET: No equivalent multi-client tests exist to validate the broadcast model. Consider adding these tests when migrating Python/.NET to protocol v3.

* rejections, consistent with standard EventEmitter / event handler semantics.
* @internal
*/
private _handleBroadcastEvent(event: SessionEvent): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

Cross-SDK consistency: This new _handleBroadcastEvent method implements the protocol v3 broadcast model for Node.js (similar implementation exists in Go's handleBroadcastEvent).

Missing in Python and .NET: Neither SDK has implemented equivalent broadcast event handling. They still use the old RPC callback pattern (tool.call / permission.request handlers in the client).

To maintain feature parity, Python and .NET need:

  1. A similar method to intercept and handle external_tool.requested / permission.requested events
  2. Logic to execute local handlers and respond via session.tools.handlePendingToolCall / session.permissions.handlePendingPermissionRequest RPCs (the generated RPC methods already exist but aren't being used)

* This must match the version expected by the copilot-agent-runtime server.
*/
export const SDK_PROTOCOL_VERSION = 2;
export const SDK_PROTOCOL_VERSION = 3;
Copy link
Contributor

Choose a reason for hiding this comment

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

Cross-SDK consistency: Protocol version bumped to 3 in Node.js and Go, but Python and .NET are still on version 2.

  • Python: python/copilot/sdk_protocol_version.py:9SDK_PROTOCOL_VERSION = 2
  • .NET: dotnet/src/SdkProtocolVersion.cs:14Version = 2

These should be updated in sync to avoid protocol mismatch issues when different language SDKs interact with the same runtime.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 7, 2026

Cross-SDK Consistency Review

This PR implements a major architectural change (protocol v2 → v3) that replaces the RPC callback pattern with a broadcast event model for tool calls and permission requests. However, the changes are only implemented in Node.js and Go, creating a breaking inconsistency with Python and .NET.

🔴 Critical Issues

1. Protocol Version Mismatch

The protocol version has been bumped to 3 in only 2 out of 4 SDKs:

  • Node.js: SDK_PROTOCOL_VERSION = 3
  • Go: SdkProtocolVersion = 3
  • Python: SDK_PROTOCOL_VERSION = 2 (unchanged)
  • .NET: Version = 2 (unchanged)

Files:

  • nodejs/src/sdkProtocolVersion.ts (changed)
  • go/sdk_protocol_version.go (changed)
  • python/copilot/sdk_protocol_version.py (needs update)
  • dotnet/src/SdkProtocolVersion.cs (needs update)

2. Broadcast Event Handling Not Implemented

Node.js and Go have added broadcast event handling, but Python and .NET still use the old RPC callback pattern:

Node.js & Go additions (✅):

  • Added _handleBroadcastEvent / handleBroadcastEvent method in session that intercepts:
    • external_tool.requested events → executes tool handler → calls session.tools.handlePendingToolCall RPC
    • permission.requested events → executes permission handler → calls session.permissions.handlePendingPermissionRequest RPC
  • Removed old RPC request handlers: tool.call, permission.request

Python & .NET still using old pattern (❌):

  • Python still registers: self._client.set_request_handler("tool.call", ...) and self._client.set_request_handler("permission.request", ...)
    (python/copilot/client.py:1357-1358)
  • .NET still registers: rpc.AddLocalRpcMethod("tool.call", ...) and rpc.AddLocalRpcMethod("permission.request", ...)
    (dotnet/src/Client.cs:1132-1133)

3. Missing E2E Tests

Multi-client E2E tests were added for Node.js and Go to verify the new broadcast behavior, but not for Python and .NET:

  • Node.js: nodejs/test/e2e/multi-client.test.ts (added)
  • Go: go/internal/e2e/multi_client_test.go (added)
  • Python: No equivalent test
  • .NET: No equivalent test

📋 Required Changes for Consistency

To bring Python and .NET into parity with Node.js and Go, the following changes are needed:

Python (python/copilot/)

  1. Update protocol version:

    # python/copilot/sdk_protocol_version.py
    SDK_PROTOCOL_VERSION = 3  # Change from 2 to 3
  2. Add broadcast event handling in session.py:

    • Add _handle_broadcast_event() method similar to Node.js's _handleBroadcastEvent()
    • Add _execute_tool_and_respond() and _execute_permission_and_respond() methods
    • Call _handle_broadcast_event() from _dispatch_event()
  3. Remove old RPC handlers in client.py:

    • Remove lines 1357-1358: set_request_handler("tool.call", ...) and set_request_handler("permission.request", ...)
    • Remove the corresponding handler methods
  4. Add multi-client E2E tests:

    • Create python/e2e/test_multi_client.py with equivalent test scenarios

.NET (dotnet/src/)

  1. Update protocol version:

    // dotnet/src/SdkProtocolVersion.cs
    private const int Version = 3;  // Change from 2 to 3
  2. Add broadcast event handling in Session.cs:

    • Add HandleBroadcastEvent() method similar to Go's handleBroadcastEvent()
    • Add ExecuteToolAndRespond() and ExecutePermissionAndRespond() methods
    • Call HandleBroadcastEvent() from event dispatch logic
  3. Remove old RPC handlers in Client.cs:

    • Remove lines 1132-1133: AddLocalRpcMethod("tool.call", ...) and AddLocalRpcMethod("permission.request", ...)
    • Remove the corresponding handler methods
  4. Add multi-client E2E tests:

    • Create equivalent multi-client test file with the same test scenarios

💡 Recommendation

This PR should either:

  1. Be expanded to include Python and .NET implementations (maintaining full cross-SDK parity), or
  2. Be marked as Part 1 with follow-up PRs tracked for Python and .NET, ensuring the runtime release is coordinated with all SDK updates

The current state will break Python and .NET SDKs when the runtime v3 is released, as they expect the old RPC callback pattern that will be removed.

Generated by SDK Consistency Review Agent for issue #686 ·

@github-actions
Copy link
Contributor

github-actions bot commented Mar 7, 2026

Cross-SDK Consistency Review

This PR implements a significant architectural change to handle tool calls and permission requests via a broadcast event model (protocol v3) instead of the RPC callback pattern (protocol v2). However, this feature has only been implemented in Node.js, Go, and .NET, while Python is missing this implementation.

What Changed

The PR replaces the old RPC callback pattern with a broadcast event model where:

  1. The runtime broadcasts external_tool.requested and permission.requested as session events to all connected clients
  2. Clients intercept these events, execute local handlers, and respond via new RPC methods:
    • session.tools.handlePendingToolCall
    • session.permissions.handlePendingPermissionRequest

Implementation Status by SDK

Node.js (nodejs/src/)

  • Removed tool.call and permission.request RPC request handlers from client.ts
  • Added _handleBroadcastEvent() in session.ts that intercepts broadcast events
  • Protocol version bumped to 3
  • Generated RPC types include new methods (session.tools.handlePendingToolCall, session.permissions.handlePendingPermissionRequest)

Go (go/)

  • Removed handleToolCallRequest and handlePermissionRequest RPC handlers from client.go
  • Added handleBroadcastEvent() in session.go
  • Protocol version bumped to 3
  • Generated RPC includes new methods

.NET (dotnet/src/)

  • Removed old RPC request handlers
  • Added HandleBroadcastEventAsync() in Session.cs
  • Protocol version bumped to 3
  • Generated RPC includes new API methods

Python (python/copilot/)

  • No changes made to Python SDK in this PR
  • Still uses the old RPC callback pattern (tool.call, permission.request handlers in client.py lines 1357-1358)
  • Protocol version remains at 2
  • Missing _handle_broadcast_event() logic in session.py
  • Generated RPC types include the new methods (lines 1486, 1497 in generated/rpc.py), but they're not being used

Impact

This creates a breaking inconsistency across SDKs:

  1. Protocol mismatch: When the runtime updates to broadcast model (protocol v3), the Python SDK will fail because it:

    • Still expects tool.call and permission.request RPC requests (which the runtime will no longer send)
    • Doesn't handle external_tool.requested and permission.requested broadcast events
    • Hasn't bumped to protocol version 3
  2. Multi-client scenarios: The new E2E tests (e.g., MultiClientTests.cs, multi_client_test.go) demonstrate multi-client tool handling and permission broadcasts. Python clients won't participate in these scenarios correctly.

Recommendation

The Python SDK needs the same changes:

  1. Update python/copilot/client.py:

    • Remove set_request_handler("tool.call", ...) and set_request_handler("permission.request", ...) (lines 1357-1358)
    • These should only be registered for protocol v2 (backward compatibility)
  2. Update python/copilot/session.py:

    • Add _handle_broadcast_event() method to intercept external_tool.requested and permission.requested events
    • Implement _execute_tool_and_respond() and _execute_permission_and_respond() to call the new RPC methods
    • Call _handle_broadcast_event() from _dispatch_event() before user handlers
  3. Update python/copilot/sdk_protocol_version.py:

    • Bump SDK_PROTOCOL_VERSION from 2 to 3
    • Or implement protocol negotiation to support both v2 and v3
  4. Add Python E2E tests:

    • Port the multi-client tests from the other SDKs

Would you like me to create a follow-up issue to track the Python implementation, or should this PR be updated to include Python changes before merging?

Generated by SDK Consistency Review Agent for issue #686 ·

if self._client2:
try:
await self._client2.stop()
except Exception:
if self._client1:
try:
await self._client1.stop()
except Exception:
@github-actions
Copy link
Contributor

github-actions bot commented Mar 7, 2026

Cross-SDK Consistency Review ✅ (with one issue)

I've reviewed this PR for consistency across all four SDK implementations (Node.js, Python, Go, and .NET). Overall, the implementation is highly consistent across all languages! 🎉

What's Consistent ✅

The PR successfully implements the protocol v3 broadcast model across all four SDKs:

  1. Event handling: All SDKs implement broadcast event handling with language-appropriate naming:

    • Node.js: _handleBroadcastEvent
    • Python: _handle_broadcast_event
    • Go: handleBroadcastEvent
    • .NET: HandleBroadcastEventAsync
  2. RPC response methods: All SDKs use the new response methods consistently:

    • session.tools.handlePendingToolCall (with language-specific casing)
    • session.permissions.handlePendingPermissionRequest (with language-specific casing)
  3. Removed legacy handlers: All SDKs correctly removed the old tool.call and permission.request RPC request handlers

  4. Protocol version: All SDK implementation files correctly bumped to version 3:

    • nodejs/src/sdkProtocolVersion.ts: SDK_PROTOCOL_VERSION = 3
    • python/copilot/sdk_protocol_version.py: SDK_PROTOCOL_VERSION = 3
    • go/sdk_protocol_version.go: SdkProtocolVersion = 3
    • dotnet/src/SdkProtocolVersion.cs: Version = 3
  5. Multi-client E2E tests: All SDKs include comprehensive multi-client tests

Critical Issue Found ⚠️

The sdk-protocol-version.json file is still at version 2, but all SDK implementations are at version 3.

This file is the source of truth for the update-protocol-version.ts script (as documented in the script header). The SDK protocol version files have a comment // Code generated by update-protocol-version.ts. DO NOT EDIT. but were manually edited instead of being generated from the JSON file.

Recommendation: Update sdk-protocol-version.json to version 3 to match the SDK implementations and maintain consistency with the documented generation process.

{
    "version": 3
}

Summary

The cross-language implementation is excellent and maintains strong API consistency. The only issue is the source protocol version file not being in sync with the generated files. Once that's fixed, this PR will have perfect cross-SDK consistency! 🚀

Generated by SDK Consistency Review Agent for issue #686 ·

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Generated by SDK Consistency Review Agent for issue #686

* This must match the version expected by the copilot-agent-runtime server.
*/
export const SDK_PROTOCOL_VERSION = 2;
export const SDK_PROTOCOL_VERSION = 3;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Consistency issue: The sdk-protocol-version.json file at the repository root is still set to version 2, but all SDK implementations are now at version 3.

According to the comment in this file (// Code generated by update-protocol-version.ts. DO NOT EDIT.), these files should be generated from sdk-protocol-version.json. However, it appears the SDK files were manually updated instead.

Recommendation: Update sdk-protocol-version.json to version 3 to match the SDK implementations:

{
    "version": 3
}

This ensures the source of truth matches the generated files and maintains consistency with the documented code generation process.

using var sub2 = session2.On(evt => client2Events.Add(evt));

// Write a file so the agent has something to edit
await File.WriteAllTextAsync(Path.Combine(Ctx.WorkDir, "protected.txt"), "protected content");
});

// Verify the file was NOT modified
var content = await File.ReadAllTextAsync(Path.Combine(Ctx.WorkDir, "protected.txt"));
@SteveSandersonMS SteveSandersonMS changed the title Node SDK: Handle tool and permission broadcasts via event model Handle tool and permission broadcasts via event model (protocol v3) Mar 7, 2026
@SteveSandersonMS SteveSandersonMS marked this pull request as ready for review March 7, 2026 12:28
Comment on lines +440 to +454
catch (Exception ex)
{
try
{
await Rpc.Tools.HandlePendingToolCallAsync(requestId, result: null, error: ex.Message);
}
catch (IOException)
{
// Connection lost or RPC error — nothing we can do
}
catch (ObjectDisposedException)
{
// Connection already disposed — nothing we can do
}
}
Comment on lines +483 to +500
catch (Exception)
{
try
{
await Rpc.Permissions.HandlePendingPermissionRequestAsync(requestId, new PermissionRequestResult
{
Kind = PermissionRequestResultKind.DeniedCouldNotRequestFromUser
});
}
catch (IOException)
{
// Connection lost or RPC error — nothing we can do
}
catch (ObjectDisposedException)
{
// Connection already disposed — nothing we can do
}
}
@github-actions
Copy link
Contributor

github-actions bot commented Mar 7, 2026

Cross-SDK Consistency Review ✅

I've reviewed this PR for consistency across all four SDK implementations (Node.js/TypeScript, Python, Go, and .NET). This is an excellent example of maintaining feature parity across a multi-language SDK repository.

Summary

This PR implements protocol v3's broadcast event model for tool calls and permission requests, replacing the previous RPC callback pattern. All four SDKs have been updated consistently.

Consistency Analysis

✅ Core Implementation

All four SDKs implement the same pattern:

  1. Protocol version bump: All SDKs updated from v2 to v3

    • Node.js: SDK_PROTOCOL_VERSION = 3
    • Python: SDK_PROTOCOL_VERSION = 3
    • Go: SdkProtocolVersion = 3
    • .NET: Version = 3
  2. Broadcast event handler: All SDKs have equivalent implementations

    • Node.js: _handleBroadcastEvent(event: SessionEvent)
    • Python: _handle_broadcast_event(self, event: SessionEvent)
    • Go: handleBroadcastEvent(event SessionEvent)
    • .NET: HandleBroadcastEventAsync(SessionEvent sessionEvent)
  3. New RPC response methods: All SDKs implement both methods

    • session.tools.handlePendingToolCall (with requestId, result, error parameters)
    • session.permissions.handlePendingPermissionRequest (with requestId, result parameters)
  4. Event types: All SDKs handle the same broadcast events

    • external_tool.requested / ExternalToolRequested / EXTERNAL_TOOL_REQUESTED
    • permission.requested / PermissionRequested / PERMISSION_REQUESTED
  5. Old handler removal: All SDKs removed the old RPC handlers

    • Removed: tool.call RPC request handler
    • Removed: permission.request RPC request handler

✅ Test Coverage

All four SDKs implement the same 5 multi-client test scenarios:

  1. Both clients see tool request and completion events
  2. One client approves permission and both see the result
  3. One client rejects permission and both see the result
  4. Two clients register different tools and agent uses both
  5. Disconnecting client removes its tools

Test file locations:

  • Node.js: nodejs/test/e2e/multi-client.test.ts
  • Python: python/e2e/test_multi_client.py
  • Go: go/internal/e2e/multi_client_test.go
  • .NET: dotnet/test/MultiClientTests.cs

✅ Generated Code

All SDKs regenerated their RPC and session event types from updated schemas:

  • Node.js: nodejs/src/generated/rpc.ts, nodejs/src/generated/session-events.ts
  • Python: python/copilot/generated/rpc.py, python/copilot/generated/session_events.py
  • Go: go/rpc/generated_rpc.go, go/generated_session_events.go
  • .NET: dotnet/src/Generated/Rpc.cs, dotnet/src/Generated/SessionEvents.cs

Conclusion

This PR maintains excellent cross-SDK consistency. The implementation follows language-specific conventions (naming, async patterns, error handling) while keeping the API surface and behavior equivalent across all four SDKs. No inconsistencies or missing features detected.

Great work! 🎉

Generated by SDK Consistency Review Agent for issue #686 ·

@SteveSandersonMS SteveSandersonMS force-pushed the stevesa/handle-broadcasts-for-permissions-and-tools branch from 51ae8be to 31d4acf Compare March 7, 2026 12:58
@github-actions
Copy link
Contributor

github-actions bot commented Mar 7, 2026

✅ Cross-SDK Consistency Review

I've analyzed PR #686 for consistency across all four SDK implementations (Node.js, Python, Go, and .NET). Here are my findings:

Summary

Excellent work! This PR implements the protocol v3 broadcast model consistently across all SDKs with proper feature parity.

Key Changes Verified ✓

1. Protocol Version Bump

All four SDKs correctly bumped from version 2 to 3:

  • ✅ Node.js: SDK_PROTOCOL_VERSION = 3
  • ✅ Python: SDK_PROTOCOL_VERSION = 3
  • ✅ Go: SdkProtocolVersion = 3
  • ✅ .NET: Version = 3

2. Broadcast Event Handling

All SDKs implement equivalent broadcast event handlers following their respective naming conventions:

  • ✅ Node.js: _handleBroadcastEvent() in session.ts
  • ✅ Python: _handle_broadcast_event() in session.py
  • ✅ Go: handleBroadcastEvent() in session.go
  • ✅ .NET: HandleBroadcastEvent() in Session.cs

Each handles the same events:

  • external_tool.requested (tool calls)
  • permission.requested (permission requests)

3. New RPC Response Methods

All SDKs implement the new response methods with semantically equivalent parameters:

Tool handling:

  • ✅ Node.js: rpc.tools.handlePendingToolCall({ requestId, result, error })
  • ✅ Python: rpc.tools.handle_pending_tool_call(params)
  • ✅ Go: RPC.Tools.HandlePendingToolCall(ctx, params)
  • ✅ .NET: Rpc.Tools.HandlePendingToolCallAsync(requestId, result, error)

Permission handling:

  • ✅ Node.js: rpc.permissions.handlePendingPermissionRequest({ requestId, decision })
  • ✅ Python: rpc.permissions.handle_pending_permission_request(params)
  • ✅ Go: RPC.Permissions.HandlePendingPermissionRequest(ctx, params)
  • ✅ .NET: Rpc.Permissions.HandlePendingPermissionRequestAsync(requestId, decision)

4. Breaking Changes Applied Uniformly

All SDKs removed the old RPC callback handlers:

  • ✅ Removed tool.call request handler
  • ✅ Removed permission.request request handler

5. Test Coverage Parity

All SDKs implement the same 5 core multi-client test scenarios:

  1. ✅ Both clients see tool request and completion events
  2. ✅ One client approves permission and both see the result
  3. ✅ One client rejects permission and both see the result
  4. ✅ Two clients register different tools and agent uses both
  5. ✅ Disconnecting client removes its tools

Language-Specific Conventions ✓

The implementations properly respect language idioms:

  • Naming: camelCase (TypeScript), snake_case (Python), PascalCase (Go/C#)
  • Async patterns: Promises (JS), async/await (Python), context.Context (Go), Task (C#)
  • Testing: Vitest (JS), pytest (Python), testing package (Go), xUnit (.NET)

Recommendation

✅ No consistency issues found.

This PR is an excellent example of maintaining feature parity across a multi-language SDK. All four implementations follow the same architectural pattern and provide equivalent functionality. The changes maintain cross-SDK consistency while respecting each language's conventions.


Automated review by SDK Consistency Agent

Generated by SDK Consistency Review Agent for issue #686 ·

@github-actions
Copy link
Contributor

github-actions bot commented Mar 7, 2026

Cross-SDK Consistency Review ✅

I've reviewed this PR for consistency across all four SDK implementations (Node.js, Python, Go, and .NET). Here's my analysis:

Summary

This PR maintains excellent cross-SDK consistency. All four SDKs have been updated to implement the protocol v3 broadcast model for tool calls and permission requests.

Key Changes Verified Across All SDKs

1. Protocol Version Bump ✅

All SDKs consistently bumped to protocol version 3:

  • Node.js: SDK_PROTOCOL_VERSION = 3
  • Python: SDK_PROTOCOL_VERSION = 3
  • Go: SdkProtocolVersion = 3
  • .NET: Version = 3

2. Broadcast Event Handling ✅

All SDKs implement the new broadcast event model consistently:

  • Node.js: _handleBroadcastEvent() intercepts external_tool.requested and permission.requested
  • Python: _handle_broadcast_event() with identical event handling logic
  • Go: handleBroadcastEvent() with switch-case on event types
  • .NET: HandleBroadcastEventAsync() with pattern matching on event types

3. RPC Method Names ✅

All SDKs use the correct RPC methods (respecting language conventions):

  • session.tools.handlePendingToolCall / handle_pending_tool_call / HandlePendingToolCall / HandlePendingToolCallAsync
  • session.permissions.handlePendingPermissionRequest / handle_pending_permission_request / HandlePendingPermissionRequest / HandlePendingPermissionRequestAsync

4. Old RPC Handler Removal ✅

  • Node.js: Removed tool.call and permission.request RPC handlers from client.ts
  • Go: Removed SetRequestHandler("tool.call", ...) and SetRequestHandler("permission.request", ...)
  • .NET: Removed OnToolCall() and OnPermissionRequest() methods (~120 lines)
  • Python: Never had these handlers (already using event-based model)

5. Error Handling ✅

All SDKs use consistent error fallback values:

  • Node: kind: "denied-no-approval-rule-and-could-not-request-from-user"
  • Python: Kind.DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER
  • Go: rpc.DeniedNoApprovalRuleAndCouldNotRequestFromUser
  • .NET: PermissionRequestResultKind.DeniedCouldNotRequestFromUser (property name that maps to the same string value)

6. E2E Test Coverage ✅

All SDKs include multi-client E2E tests:

  • nodejs/test/e2e/multi-client.test.ts
  • python/e2e/test_multi_client.py
  • go/internal/e2e/multi_client_test.go
  • dotnet/test/MultiClientTests.cs

Conclusion

This PR demonstrates exemplary cross-language consistency. The API surface, behavior, and test coverage are parallel across all four SDK implementations, with differences limited to idiomatic language conventions (camelCase vs snake_case vs PascalCase).

No consistency issues found. ✅

Generated by SDK Consistency Review Agent for issue #686 ·

else:
self._process.kill()
self._process = None
except Exception:
@SteveSandersonMS SteveSandersonMS force-pushed the stevesa/handle-broadcasts-for-permissions-and-tools branch from ce65917 to 8d55ec4 Compare March 7, 2026 13:31
@SteveSandersonMS SteveSandersonMS merged commit 1653812 into main Mar 7, 2026
33 checks passed
@SteveSandersonMS SteveSandersonMS deleted the stevesa/handle-broadcasts-for-permissions-and-tools branch March 7, 2026 13:38
@github-actions
Copy link
Contributor

github-actions bot commented Mar 7, 2026

✅ Cross-SDK Consistency Review

I've reviewed this PR for consistency across all four SDK implementations (Node.js, Python, Go, and .NET).

Summary

This PR implements protocol v3 with the broadcast event model for tool calls and permission requests. The implementation is highly consistent across all SDKs with only minor differences that are appropriate for each language's idioms.


What's Consistent

1. Protocol Version Update

All four SDKs bump from v2 → v3:

  • nodejs/src/sdkProtocolVersion.ts
  • python/copilot/sdk_protocol_version.py
  • go/sdk_protocol_version.go
  • dotnet/src/SdkProtocolVersion.cs

2. RPC Handler Removal

All SDKs consistently removed the old RPC request handlers:

  • tool.call handler removed from Client
  • permission.request handler removed from Client

3. Broadcast Event Handling

All SDKs implement broadcast event handling in their Session classes:

  • ✅ Node.js: _handleBroadcastEvent() in session.ts
  • ✅ Python: _handle_broadcast_event() in session.py
  • ✅ Go: handleBroadcastEvent() in session.go
  • ✅ .NET: HandleBroadcastEventAsync() in Session.cs

All handle the same two event types:

  • external_tool.requested
  • permission.requested

4. RPC Response Methods

All SDKs use identical RPC method names to respond:

  • session.tools.handlePendingToolCall(requestId, result?, error?)
  • session.permissions.handlePendingPermissionRequest(requestId, result)

5. Test Coverage

All four SDKs include comprehensive multi-client E2E tests (5 tests each):

  • ✅ Both clients see tool request and completion events
  • ✅ One client approves permission and both see the result
  • ✅ One client rejects permission and both see the result
  • ✅ Two clients register different tools and agent uses both
  • ✅ Disconnecting client removes its tools

All tests share the same snapshot files under test/snapshots/multi_client/.


🔍 Minor Differences (Expected & Appropriate)

These differences reflect language idioms and best practices:

  1. Async Execution Patterns:

    • Node.js: void this._executeToolAndRespond() (fire-and-forget)
    • Python: asyncio.ensure_future() (async task)
    • Go: go s.executeToolAndRespond() (goroutine)
    • .NET: async void HandleBroadcastEventAsync() (async void method)
  2. Naming Conventions (as expected):

    • Node.js: camelCase (e.g., _handleBroadcastEvent)
    • Python: snake_case (e.g., _handle_broadcast_event)
    • Go: camelCase for private (e.g., handleBroadcastEvent)
    • .NET: PascalCase (e.g., HandleBroadcastEventAsync)
  3. Error Handling: Each SDK uses language-appropriate patterns (try/catch in .NET/Node, defer/recover in Go, exception handling in Python)


📊 Implementation Quality

Aspect Status
Feature Parity ✅ All SDKs implement the same functionality
API Consistency ✅ Method names and signatures are parallel
Test Coverage ✅ All SDKs have identical test scenarios
Protocol Version ✅ All bumped to v3
Documentation ✅ Comments explain the v3 broadcast model

🎯 Verdict

No consistency issues found. This PR maintains excellent cross-SDK consistency while respecting language-specific idioms. The broadcast event model is implemented equivalently across all four languages, and the test coverage ensures behavioral parity.

Great work maintaining consistency across this complex multi-SDK change! 🚀

Generated by SDK Consistency Review Agent for issue #686 ·

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