Skip to content

Commit 74b8784

Browse files
jmoseleyCopilot
andcommitted
Fix CI after shell notification merge
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent b24b24a commit 74b8784

File tree

10 files changed

+81
-25
lines changed

10 files changed

+81
-25
lines changed

dotnet/src/Generated/Rpc.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,11 +1325,13 @@ public class SessionRpc
13251325
{
13261326
private readonly JsonRpc _rpc;
13271327
private readonly string _sessionId;
1328+
private readonly Action<string>? _onShellExec;
13281329

1329-
internal SessionRpc(JsonRpc rpc, string sessionId)
1330+
internal SessionRpc(JsonRpc rpc, string sessionId, Action<string>? onShellExec = null)
13301331
{
13311332
_rpc = rpc;
13321333
_sessionId = sessionId;
1334+
_onShellExec = onShellExec;
13331335
Model = new ModelApi(rpc, sessionId);
13341336
Mode = new ModeApi(rpc, sessionId);
13351337
Plan = new PlanApi(rpc, sessionId);
@@ -1345,7 +1347,7 @@ internal SessionRpc(JsonRpc rpc, string sessionId)
13451347
Commands = new CommandsApi(rpc, sessionId);
13461348
Ui = new UiApi(rpc, sessionId);
13471349
Permissions = new PermissionsApi(rpc, sessionId);
1348-
Shell = new ShellApi(rpc, sessionId);
1350+
Shell = new ShellApi(rpc, sessionId, _onShellExec);
13491351
}
13501352

13511353
/// <summary>Model APIs.</summary>
@@ -1849,18 +1851,22 @@ public class ShellApi
18491851
{
18501852
private readonly JsonRpc _rpc;
18511853
private readonly string _sessionId;
1854+
private readonly Action<string>? _onExec;
18521855

1853-
internal ShellApi(JsonRpc rpc, string sessionId)
1856+
internal ShellApi(JsonRpc rpc, string sessionId, Action<string>? onExec = null)
18541857
{
18551858
_rpc = rpc;
18561859
_sessionId = sessionId;
1860+
_onExec = onExec;
18571861
}
18581862

18591863
/// <summary>Calls "session.shell.exec".</summary>
18601864
public async Task<SessionShellExecResult> ExecAsync(string command, string? cwd = null, double? timeout = null, CancellationToken cancellationToken = default)
18611865
{
18621866
var request = new SessionShellExecRequest { SessionId = _sessionId, Command = command, Cwd = cwd, Timeout = timeout };
1863-
return await CopilotClient.InvokeRpcAsync<SessionShellExecResult>(_rpc, "session.shell.exec", [request], cancellationToken);
1867+
var result = await CopilotClient.InvokeRpcAsync<SessionShellExecResult>(_rpc, "session.shell.exec", [request], cancellationToken);
1868+
_onExec?.Invoke(result.ProcessId);
1869+
return result;
18641870
}
18651871

18661872
/// <summary>Calls "session.shell.kill".</summary>
@@ -1971,4 +1977,4 @@ public async Task<SessionShellKillResult> KillAsync(string processId, SessionShe
19711977
[JsonSerializable(typeof(Tool))]
19721978
[JsonSerializable(typeof(ToolsListRequest))]
19731979
[JsonSerializable(typeof(ToolsListResult))]
1974-
internal partial class RpcJsonContext : JsonSerializerContext;
1980+
internal partial class RpcJsonContext : JsonSerializerContext;

dotnet/src/Session.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public sealed partial class CopilotSession : IAsyncDisposable
9393
/// <summary>
9494
/// Gets the typed RPC client for session-scoped methods.
9595
/// </summary>
96-
public SessionRpc Rpc => _sessionRpc ??= new SessionRpc(_rpc, SessionId);
96+
public SessionRpc Rpc => _sessionRpc ??= new SessionRpc(_rpc, SessionId, TrackShellProcess);
9797

9898
/// <summary>
9999
/// Gets the path to the session workspace directory when infinite sessions are enabled.

dotnet/src/Types.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2099,6 +2099,12 @@ public class ShellOutputNotification
20992099
[JsonPropertyName("processId")]
21002100
public string ProcessId { get; set; } = string.Empty;
21012101

2102+
/// <summary>
2103+
/// Identifier of the session that produced this notification, when provided by the runtime.
2104+
/// </summary>
2105+
[JsonPropertyName("sessionId")]
2106+
public string? SessionId { get; set; }
2107+
21022108
/// <summary>
21032109
/// Which output stream produced this chunk ("stdout" or "stderr").
21042110
/// </summary>
@@ -2124,6 +2130,12 @@ public class ShellExitNotification
21242130
[JsonPropertyName("processId")]
21252131
public string ProcessId { get; set; } = string.Empty;
21262132

2133+
/// <summary>
2134+
/// Identifier of the session that produced this notification, when provided by the runtime.
2135+
/// </summary>
2136+
[JsonPropertyName("sessionId")]
2137+
public string? SessionId { get; set; }
2138+
21272139
/// <summary>
21282140
/// Process exit code (0 = success).
21292141
/// </summary>

go/client.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,15 +1466,23 @@ func (c *Client) handleSessionEvent(req sessionEventRequest) {
14661466
}
14671467

14681468
func (c *Client) handleShellOutput(notification ShellOutputNotification) {
1469-
session, ok := c.getShellNotificationSession(notification.SessionID, notification.ProcessID)
1469+
var sessionID string
1470+
if notification.SessionID != nil {
1471+
sessionID = *notification.SessionID
1472+
}
1473+
session, ok := c.getShellNotificationSession(sessionID, notification.ProcessID)
14701474

14711475
if ok {
14721476
session.dispatchShellOutput(notification)
14731477
}
14741478
}
14751479

14761480
func (c *Client) handleShellExit(notification ShellExitNotification) {
1477-
session, ok := c.getShellNotificationSession(notification.SessionID, notification.ProcessID)
1481+
var sessionID string
1482+
if notification.SessionID != nil {
1483+
sessionID = *notification.SessionID
1484+
}
1485+
session, ok := c.getShellNotificationSession(sessionID, notification.ProcessID)
14781486

14791487
if ok {
14801488
session.dispatchShellExit(notification)

go/rpc/generated_rpc.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,6 +1383,7 @@ func (a *PermissionsRpcApi) HandlePendingPermissionRequest(ctx context.Context,
13831383
type ShellRpcApi struct {
13841384
client *jsonrpc2.Client
13851385
sessionID string
1386+
onExec func(string)
13861387
}
13871388

13881389
func (a *ShellRpcApi) Exec(ctx context.Context, params *SessionShellExecParams) (*SessionShellExecResult, error) {
@@ -1404,6 +1405,9 @@ func (a *ShellRpcApi) Exec(ctx context.Context, params *SessionShellExecParams)
14041405
if err := json.Unmarshal(raw, &result); err != nil {
14051406
return nil, err
14061407
}
1408+
if a.onExec != nil {
1409+
a.onExec(result.ProcessID)
1410+
}
14071411
return &result, nil
14081412
}
14091413

@@ -1473,7 +1477,11 @@ func (a *SessionRpc) Log(ctx context.Context, params *SessionLogParams) (*Sessio
14731477
return &result, nil
14741478
}
14751479

1476-
func NewSessionRpc(client *jsonrpc2.Client, sessionID string) *SessionRpc {
1480+
func NewSessionRpc(client *jsonrpc2.Client, sessionID string, onShellExec ...func(string)) *SessionRpc {
1481+
var shellExecHandler func(string)
1482+
if len(onShellExec) > 0 {
1483+
shellExecHandler = onShellExec[0]
1484+
}
14771485
return &SessionRpc{client: client, sessionID: sessionID,
14781486
Model: &ModelRpcApi{client: client, sessionID: sessionID},
14791487
Mode: &ModeRpcApi{client: client, sessionID: sessionID},
@@ -1490,6 +1498,6 @@ func NewSessionRpc(client *jsonrpc2.Client, sessionID string) *SessionRpc {
14901498
Commands: &CommandsRpcApi{client: client, sessionID: sessionID},
14911499
Ui: &UiRpcApi{client: client, sessionID: sessionID},
14921500
Permissions: &PermissionsRpcApi{client: client, sessionID: sessionID},
1493-
Shell: &ShellRpcApi{client: client, sessionID: sessionID},
1501+
Shell: &ShellRpcApi{client: client, sessionID: sessionID, onExec: shellExecHandler},
14941502
}
14951503
}

go/session.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ func newSession(sessionID string, client *jsonrpc2.Client, workspacePath string)
112112
toolHandlers: make(map[string]ToolHandler),
113113
eventCh: make(chan SessionEvent, 128),
114114
trackedProcessIDs: make(map[string]struct{}),
115-
RPC: rpc.NewSessionRpc(client, sessionID),
116115
}
116+
s.RPC = rpc.NewSessionRpc(client, sessionID, s.trackShellProcess)
117117
go s.processEvents()
118118
return s
119119
}
@@ -674,6 +674,16 @@ func (s *Session) dispatchShellExit(notification ShellExitNotification) {
674674
}
675675
}
676676

677+
// trackShellProcess starts tracking a shell process ID.
678+
func (s *Session) trackShellProcess(processID string) {
679+
s.trackedProcessMux.Lock()
680+
s.trackedProcessIDs[processID] = struct{}{}
681+
s.trackedProcessMux.Unlock()
682+
if s.registerShellProc != nil {
683+
s.registerShellProc(processID, s)
684+
}
685+
}
686+
677687
// untrackShellProcess stops tracking a shell process ID.
678688
func (s *Session) untrackShellProcess(processID string) {
679689
s.trackedProcessMux.Lock()

go/types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,8 @@ const (
755755
type ShellOutputNotification struct {
756756
// ProcessID is the process identifier returned by shell.exec.
757757
ProcessID string `json:"processId"`
758+
// SessionID is the optional session identifier for direct routing when provided by the runtime.
759+
SessionID *string `json:"sessionId,omitempty"`
758760
// Stream indicates which output stream produced this chunk.
759761
Stream ShellOutputStream `json:"stream"`
760762
// Data is the output data (UTF-8 string).
@@ -766,6 +768,8 @@ type ShellOutputNotification struct {
766768
type ShellExitNotification struct {
767769
// ProcessID is the process identifier returned by shell.exec.
768770
ProcessID string `json:"processId"`
771+
// SessionID is the optional session identifier for direct routing when provided by the runtime.
772+
SessionID *string `json:"sessionId,omitempty"`
769773
// ExitCode is the process exit code (0 = success).
770774
ExitCode int `json:"exitCode"`
771775
}

python/copilot/generated/rpc.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2836,14 +2836,25 @@ async def handle_pending_permission_request(self, params: SessionPermissionsHand
28362836

28372837

28382838
class ShellApi:
2839-
def __init__(self, client: "JsonRpcClient", session_id: str):
2839+
def __init__(
2840+
self,
2841+
client: "JsonRpcClient",
2842+
session_id: str,
2843+
on_exec: Callable[[str], None] | None = None,
2844+
):
28402845
self._client = client
28412846
self._session_id = session_id
2847+
self._on_exec = on_exec
28422848

28432849
async def exec(self, params: SessionShellExecParams, *, timeout: float | None = None) -> SessionShellExecResult:
28442850
params_dict = {k: v for k, v in params.to_dict().items() if v is not None}
28452851
params_dict["sessionId"] = self._session_id
2846-
return SessionShellExecResult.from_dict(await self._client.request("session.shell.exec", params_dict, **_timeout_kwargs(timeout)))
2852+
result = SessionShellExecResult.from_dict(
2853+
await self._client.request("session.shell.exec", params_dict, **_timeout_kwargs(timeout))
2854+
)
2855+
if self._on_exec is not None:
2856+
self._on_exec(result.process_id)
2857+
return result
28472858

28482859
async def kill(self, params: SessionShellKillParams, *, timeout: float | None = None) -> SessionShellKillResult:
28492860
params_dict = {k: v for k, v in params.to_dict().items() if v is not None}
@@ -2853,7 +2864,12 @@ async def kill(self, params: SessionShellKillParams, *, timeout: float | None =
28532864

28542865
class SessionRpc:
28552866
"""Typed session-scoped RPC methods."""
2856-
def __init__(self, client: "JsonRpcClient", session_id: str):
2867+
def __init__(
2868+
self,
2869+
client: "JsonRpcClient",
2870+
session_id: str,
2871+
on_shell_exec: Callable[[str], None] | None = None,
2872+
):
28572873
self._client = client
28582874
self._session_id = session_id
28592875
self.model = ModelApi(client, session_id)
@@ -2871,10 +2887,9 @@ def __init__(self, client: "JsonRpcClient", session_id: str):
28712887
self.commands = CommandsApi(client, session_id)
28722888
self.ui = UiApi(client, session_id)
28732889
self.permissions = PermissionsApi(client, session_id)
2874-
self.shell = ShellApi(client, session_id)
2890+
self.shell = ShellApi(client, session_id, on_shell_exec)
28752891

28762892
async def log(self, params: SessionLogParams, *, timeout: float | None = None) -> SessionLogResult:
28772893
params_dict = {k: v for k, v in params.to_dict().items() if v is not None}
28782894
params_dict["sessionId"] = self._session_id
28792895
return SessionLogResult.from_dict(await self._client.request("session.log", params_dict, **_timeout_kwargs(timeout)))
2880-

python/copilot/session.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,7 @@ def __init__(self, session_id: str, client: Any, workspace_path: str | None = No
120120
def rpc(self) -> SessionRpc:
121121
"""Typed session-scoped RPC methods."""
122122
if self._rpc is None:
123-
self._rpc = SessionRpc(self._client, self.session_id)
124-
original_exec = self._rpc.shell.exec
125-
126-
async def exec_with_tracking(params, *, timeout=None):
127-
result = await original_exec(params, timeout=timeout)
128-
self._track_shell_process(result.process_id)
129-
return result
130-
131-
self._rpc.shell.exec = exec_with_tracking
123+
self._rpc = SessionRpc(self._client, self.session_id, self._track_shell_process)
132124
return self._rpc
133125

134126
@property

python/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ dev = [
3939
"pytest-asyncio>=0.21.0",
4040
"pytest-timeout>=2.0.0",
4141
"httpx>=0.24.0",
42+
"opentelemetry-api>=1.0.0",
4243
]
4344
telemetry = [
4445
"opentelemetry-api>=1.0.0",

0 commit comments

Comments
 (0)