Skip to content

Commit 843b644

Browse files
stephentoubCopilot
andcommitted
Merge upstream/main
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2 parents 506427d + b9f746a commit 843b644

6 files changed

Lines changed: 49 additions & 87 deletions

File tree

.github/workflows/copilot-setup-steps.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ jobs:
7777
# Install Python dependencies
7878
- name: Install Python dependencies
7979
working-directory: ./python
80-
run: uv sync --locked --all-extras --dev
80+
run: uv sync --all-extras --dev
8181

8282
# Install Go dependencies
8383
- name: Install Go dependencies

dotnet/src/ActionDisposable.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
namespace GitHub.Copilot.SDK;
6+
7+
/// <summary>
8+
/// A disposable that invokes an action when disposed.
9+
/// </summary>
10+
internal sealed class ActionDisposable : IDisposable
11+
{
12+
private Action? _action;
13+
14+
public ActionDisposable(Action action)
15+
{
16+
_action = action;
17+
}
18+
19+
public void Dispose()
20+
{
21+
var action = Interlocked.Exchange(ref _action, null);
22+
action?.Invoke();
23+
}
24+
}

dotnet/src/Client.cs

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,22 +1596,3 @@ public class ToolResultAIContent(ToolResultObject toolResult) : AIContent
15961596
{
15971597
public ToolResultObject Result => toolResult;
15981598
}
1599-
1600-
/// <summary>
1601-
/// A disposable that invokes an action when disposed.
1602-
/// </summary>
1603-
internal sealed class ActionDisposable : IDisposable
1604-
{
1605-
private Action? _action;
1606-
1607-
public ActionDisposable(Action action)
1608-
{
1609-
_action = action;
1610-
}
1611-
1612-
public void Dispose()
1613-
{
1614-
var action = Interlocked.Exchange(ref _action, null);
1615-
action?.Invoke();
1616-
}
1617-
}

dotnet/src/Session.cs

Lines changed: 19 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,18 @@ namespace GitHub.Copilot.SDK;
4545
/// </example>
4646
public partial class CopilotSession : IAsyncDisposable
4747
{
48-
private readonly HashSet<SessionEventHandler> _eventHandlers = new();
48+
/// <summary>
49+
/// Multicast delegate used as a thread-safe, insertion-ordered handler list.
50+
/// The compiler-generated add/remove accessors use a lock-free CAS loop over the backing field.
51+
/// Dispatch reads the field once (inherent snapshot, no allocation).
52+
/// Expected handler count is small (typically 1–3), so Delegate.Combine/Remove cost is negligible.
53+
/// </summary>
54+
private event SessionEventHandler? _eventHandlers;
4955
private readonly Dictionary<string, AIFunction> _toolHandlers = new();
5056
private readonly JsonRpc _rpc;
5157
private readonly CopilotTelemetry.AgentTurnTracker? _turnTracker;
52-
private PermissionRequestHandler? _permissionHandler;
53-
private readonly SemaphoreSlim _permissionHandlerLock = new(1, 1);
54-
private UserInputHandler? _userInputHandler;
55-
private readonly SemaphoreSlim _userInputHandlerLock = new(1, 1);
58+
private volatile PermissionRequestHandler? _permissionHandler;
59+
private volatile UserInputHandler? _userInputHandler;
5660
private SessionHooks? _hooks;
5761
private readonly SemaphoreSlim _hooksLock = new(1, 1);
5862
private SessionRpc? _sessionRpc;
@@ -292,8 +296,8 @@ void Handler(SessionEvent evt)
292296
/// </example>
293297
public IDisposable On(SessionEventHandler handler)
294298
{
295-
_eventHandlers.Add(handler);
296-
return new OnDisposeCall(() => _eventHandlers.Remove(handler));
299+
_eventHandlers += handler;
300+
return new ActionDisposable(() => _eventHandlers -= handler);
297301
}
298302

299303
/// <summary>
@@ -307,11 +311,8 @@ internal void DispatchEvent(SessionEvent sessionEvent)
307311
{
308312
_turnTracker?.ProcessEvent(sessionEvent);
309313

310-
foreach (var handler in _eventHandlers.ToArray())
311-
{
312-
// We allow handler exceptions to propagate so they are not lost
313-
handler(sessionEvent);
314-
}
314+
// Reading the field once gives us a snapshot; delegates are immutable.
315+
_eventHandlers?.Invoke(sessionEvent);
315316
}
316317

317318
/// <summary>
@@ -349,15 +350,7 @@ internal void RegisterTools(ICollection<AIFunction> tools)
349350
/// </remarks>
350351
internal void RegisterPermissionHandler(PermissionRequestHandler handler)
351352
{
352-
_permissionHandlerLock.Wait();
353-
try
354-
{
355-
_permissionHandler = handler;
356-
}
357-
finally
358-
{
359-
_permissionHandlerLock.Release();
360-
}
353+
_permissionHandler = handler;
361354
}
362355

363356
/// <summary>
@@ -367,16 +360,7 @@ internal void RegisterPermissionHandler(PermissionRequestHandler handler)
367360
/// <returns>A task that resolves with the permission decision.</returns>
368361
internal async Task<PermissionRequestResult> HandlePermissionRequestAsync(JsonElement permissionRequestData)
369362
{
370-
await _permissionHandlerLock.WaitAsync();
371-
PermissionRequestHandler? handler;
372-
try
373-
{
374-
handler = _permissionHandler;
375-
}
376-
finally
377-
{
378-
_permissionHandlerLock.Release();
379-
}
363+
var handler = _permissionHandler;
380364

381365
if (handler == null)
382366
{
@@ -403,15 +387,7 @@ internal async Task<PermissionRequestResult> HandlePermissionRequestAsync(JsonEl
403387
/// <param name="handler">The handler to invoke when user input is requested.</param>
404388
internal void RegisterUserInputHandler(UserInputHandler handler)
405389
{
406-
_userInputHandlerLock.Wait();
407-
try
408-
{
409-
_userInputHandler = handler;
410-
}
411-
finally
412-
{
413-
_userInputHandlerLock.Release();
414-
}
390+
_userInputHandler = handler;
415391
}
416392

417393
/// <summary>
@@ -421,16 +397,7 @@ internal void RegisterUserInputHandler(UserInputHandler handler)
421397
/// <returns>A task that resolves with the user's response.</returns>
422398
internal async Task<UserInputResponse> HandleUserInputRequestAsync(UserInputRequest request)
423399
{
424-
await _userInputHandlerLock.WaitAsync();
425-
UserInputHandler? handler;
426-
try
427-
{
428-
handler = _userInputHandler;
429-
}
430-
finally
431-
{
432-
_userInputHandlerLock.Release();
433-
}
400+
var handler = _userInputHandler;
434401

435402
if (handler == null)
436403
{
@@ -635,24 +602,11 @@ await InvokeRpcAsync<object>(
635602
// Connection is broken or closed
636603
}
637604

638-
_eventHandlers.Clear();
605+
_eventHandlers = null;
639606
_toolHandlers.Clear();
640607
_turnTracker?.CompleteOnDispose();
641608

642-
await _permissionHandlerLock.WaitAsync();
643-
try
644-
{
645-
_permissionHandler = null;
646-
}
647-
finally
648-
{
649-
_permissionHandlerLock.Release();
650-
}
651-
}
652-
653-
private class OnDisposeCall(Action callback) : IDisposable
654-
{
655-
public void Dispose() => callback();
609+
_permissionHandler = null;
656610
}
657611

658612
internal record SendMessageRequest

justfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ install:
7878
@cd python && uv pip install -e ".[dev]"
7979
@cd go && go mod download
8080
@cd dotnet && dotnet restore
81+
@cd test/harness && npm ci --ignore-scripts
8182
@echo "✅ All dependencies installed"
8283

8384
# Run interactive SDK playground

python/copilot/types.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,10 @@ class PermissionRequestResult(TypedDict, total=False):
215215

216216
class PermissionHandler:
217217
@staticmethod
218-
def approve_all(request: Any, invocation: Any) -> dict:
219-
return {"kind": "approved"}
218+
def approve_all(
219+
request: PermissionRequest, invocation: dict[str, str]
220+
) -> PermissionRequestResult:
221+
return PermissionRequestResult(kind="approved")
220222

221223

222224
# ============================================================================

0 commit comments

Comments
 (0)