From be0899a49ed78537fa06f5cfb4c267dfffd438f0 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 17 Feb 2026 10:20:50 +0000 Subject: [PATCH 1/6] chore: update @github/copilot to 0.0.411-0 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- nodejs/package-lock.json | 58 ++++++++++++++++++++-------------------- nodejs/package.json | 2 +- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index fb3a5f91..22e500d5 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.8", "license": "MIT", "dependencies": { - "@github/copilot": "^0.0.409", + "@github/copilot": "^0.0.411-0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -31,7 +31,7 @@ "vitest": "^4.0.18" }, "engines": { - "node": ">=24.0.0" + "node": ">=20.0.0" } }, "node_modules/@apidevtools/json-schema-ref-parser": { @@ -662,26 +662,26 @@ } }, "node_modules/@github/copilot": { - "version": "0.0.409", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-0.0.409.tgz", - "integrity": "sha512-rkYWOKjTSuGg99KsgmA0QAP4X2cpJzAYk6lZDlVxKPhuLP03wC5E+jLctrSLjpxhX32p9n13rm1+7Jun80a1hw==", + "version": "0.0.411-0", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-0.0.411-0.tgz", + "integrity": "sha512-uGjkCOTv5X4Qq8UU88OEBHC6IMCOTa/9F6QY08KX/209zBho8bZaq018i8e/PBGeKk4oTk3y3t7lQ+mR6Kpmiw==", "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "0.0.409", - "@github/copilot-darwin-x64": "0.0.409", - "@github/copilot-linux-arm64": "0.0.409", - "@github/copilot-linux-x64": "0.0.409", - "@github/copilot-win32-arm64": "0.0.409", - "@github/copilot-win32-x64": "0.0.409" + "@github/copilot-darwin-arm64": "0.0.411-0", + "@github/copilot-darwin-x64": "0.0.411-0", + "@github/copilot-linux-arm64": "0.0.411-0", + "@github/copilot-linux-x64": "0.0.411-0", + "@github/copilot-win32-arm64": "0.0.411-0", + "@github/copilot-win32-x64": "0.0.411-0" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "0.0.409", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-0.0.409.tgz", - "integrity": "sha512-yjrrp++UNNvRoWsZ1+UioBqb3DEVxL5M5ePnMO5/Sf1sngxh0y5P9P6ePFZU4PVlM5BgC38DtrcauZaKf/oArQ==", + "version": "0.0.411-0", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-0.0.411-0.tgz", + "integrity": "sha512-5QFc63uzv5xtYoHGy9IVg5PMgqA1ELwleUMq4C3iQofDKN68QGDOx2lONfyIBuPUoJba7I7ca2cxFjU0CjWYBA==", "cpu": [ "arm64" ], @@ -695,9 +695,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "0.0.409", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-0.0.409.tgz", - "integrity": "sha512-EhLfY5DGU/BZmwjVcfnwKuJA7BxS9zdNCGeynUq7z/SI93ziastFqOddUX4D+ySz6yMrrXieN8cUKgzAlRCOJg==", + "version": "0.0.411-0", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-0.0.411-0.tgz", + "integrity": "sha512-JQBhMmdiu/Rk7N9jK41oiF1t1GBDD3ZM7ZtFlhZFY56Koh2JScehvJz6YISuvIjoahQagZruQtyFdsDGeY+3zg==", "cpu": [ "x64" ], @@ -711,9 +711,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "0.0.409", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-0.0.409.tgz", - "integrity": "sha512-O7b/9LmBO8ljPqNngonx+v5d3cOs6HKvj2E9f5/Flb9Uw2lut7g6KGerfDYCMZUpvFCMDfbZSBJD3SDuJj1uPg==", + "version": "0.0.411-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-0.0.411-0.tgz", + "integrity": "sha512-YvT1S2jgBWlAvEdpVVgVX6R1PL3lGar88QnckjYGGQE1SYOJR69aJCj3zetuagRDNv+TmHEet1MP0Ql1UNUjyQ==", "cpu": [ "arm64" ], @@ -727,9 +727,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "0.0.409", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-0.0.409.tgz", - "integrity": "sha512-zSfFqyPxNaBE5/ClrSjsKxhhTpJaVOqSJY0q87iV9fw6xwdzcJ1/FlZGKjE7W8YVb4tdJx+OBMjQCU8WYewF1A==", + "version": "0.0.411-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-0.0.411-0.tgz", + "integrity": "sha512-vS+ai+FNiYKlfRjqb5KCRCRhTO+BtXOjBezGQSCduT2nhOk4R0AeghdwzosVOcs0u2lrkx1W+WXFGunwEAKwiw==", "cpu": [ "x64" ], @@ -743,9 +743,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "0.0.409", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-0.0.409.tgz", - "integrity": "sha512-VizZsdK7L3ym/OR4wahiFx+6hFtaOYN9qvsHmNSo8pb65AZ6ORdRnCPE7w9ZejMpdNEa6x6WqHfxDKJlF85zyA==", + "version": "0.0.411-0", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-0.0.411-0.tgz", + "integrity": "sha512-vKIhwgSQlXcrGeYFh4nmzTOr0C0KJviFDmFQFMIst2gpqUdC/YM1K0nTGwKzTxIGVJTLYozBBRtoWXMK42W7Ng==", "cpu": [ "arm64" ], @@ -759,9 +759,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "0.0.409", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-0.0.409.tgz", - "integrity": "sha512-c6dP3XRFk550PmH1Vxe7n/bStNSLnVGH5B+ErUKXk/SPqmZ59pyoa7H2USNdoC6Nav5tkwYYR1vwNZRy+iKvrA==", + "version": "0.0.411-0", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-0.0.411-0.tgz", + "integrity": "sha512-VG71mL0shuHdR2XCj4859oAXWF1KPKTxP9ICWqH7Jh6EcknjPrec4097g1SgeS0lYmK/irCVBk2qlvrIDjGAqw==", "cpu": [ "x64" ], diff --git a/nodejs/package.json b/nodejs/package.json index a84c4c2d..65a17d58 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -40,7 +40,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^0.0.409", + "@github/copilot": "^0.0.411-0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, From de7d87cfc4b9ed853d8328bd14af634da84b2e74 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 17 Feb 2026 10:22:03 +0000 Subject: [PATCH 2/6] chore: regenerate SDK types for all languages Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Generated/Rpc.cs | 271 +++++++++++++ dotnet/src/Generated/SessionEvents.cs | 89 +++++ go/generated_session_events.go | 135 ++++--- go/rpc/generated_rpc.go | 244 +++++++++++- nodejs/src/generated/rpc.ts | 165 ++++++++ nodejs/src/generated/session-events.ts | 35 ++ python/copilot/generated/rpc.py | 436 +++++++++++++++++++++ python/copilot/generated/session_events.py | 33 +- 8 files changed, 1342 insertions(+), 66 deletions(-) diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index c88ae790..03eb210b 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -216,6 +216,136 @@ internal class SwitchToRequest public string ModelId { get; set; } = string.Empty; } +public class SessionModeGetResult +{ + /// The current agent mode. + [JsonPropertyName("mode")] + public string Mode { get; set; } = string.Empty; +} + +internal class GetRequest +{ + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +public class SessionModeSetResult +{ + /// The agent mode after switching. + [JsonPropertyName("mode")] + public string Mode { get; set; } = string.Empty; +} + +internal class SetRequest +{ + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + [JsonPropertyName("mode")] + public string Mode { get; set; } = string.Empty; +} + +public class SessionPlanReadResult +{ + /// Whether plan.md exists in the workspace + [JsonPropertyName("exists")] + public bool Exists { get; set; } + + /// The content of plan.md, or null if it does not exist + [JsonPropertyName("content")] + public object Content { get; set; } +} + +internal class ReadRequest +{ + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +public class SessionPlanUpdateResult +{ +} + +internal class UpdateRequest +{ + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; +} + +public class SessionPlanDeleteResult +{ +} + +internal class DeleteRequest +{ + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +public class SessionWorkspaceListFilesResult +{ + /// Relative file paths in the workspace files directory + [JsonPropertyName("files")] + public List Files { get; set; } = new(); +} + +internal class ListFilesRequest +{ + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +public class SessionWorkspaceReadFileResult +{ + /// File content as a UTF-8 string + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; +} + +internal class ReadFileRequest +{ + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; +} + +public class SessionWorkspaceCreateFileResult +{ +} + +internal class CreateFileRequest +{ + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; +} + +public class SessionFleetStartResult +{ + /// Whether fleet mode was successfully activated + [JsonPropertyName("started")] + public bool Started { get; set; } +} + +internal class StartRequest +{ + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + [JsonPropertyName("prompt")] + public string? Prompt { get; set; } +} + /// Typed server-scoped RPC methods (no session required). public class ServerRpc { @@ -309,9 +439,21 @@ internal SessionRpc(JsonRpc rpc, string sessionId) _rpc = rpc; _sessionId = sessionId; Model = new ModelApi(rpc, sessionId); + Mode = new ModeApi(rpc, sessionId); + Plan = new PlanApi(rpc, sessionId); + Workspace = new WorkspaceApi(rpc, sessionId); + Fleet = new FleetApi(rpc, sessionId); } public ModelApi Model { get; } + + public ModeApi Mode { get; } + + public PlanApi Plan { get; } + + public WorkspaceApi Workspace { get; } + + public FleetApi Fleet { get; } } public class ModelApi @@ -340,13 +482,128 @@ public async Task SwitchToAsync(string modelId, Canc } } +public class ModeApi +{ + private readonly JsonRpc _rpc; + private readonly string _sessionId; + + internal ModeApi(JsonRpc rpc, string sessionId) + { + _rpc = rpc; + _sessionId = sessionId; + } + + /// Calls "session.mode.get". + public async Task GetAsync(CancellationToken cancellationToken = default) + { + var request = new GetRequest { SessionId = _sessionId }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.mode.get", [request], cancellationToken); + } + + /// Calls "session.mode.set". + public async Task SetAsync(string mode, CancellationToken cancellationToken = default) + { + var request = new SetRequest { SessionId = _sessionId, Mode = mode }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.mode.set", [request], cancellationToken); + } +} + +public class PlanApi +{ + private readonly JsonRpc _rpc; + private readonly string _sessionId; + + internal PlanApi(JsonRpc rpc, string sessionId) + { + _rpc = rpc; + _sessionId = sessionId; + } + + /// Calls "session.plan.read". + public async Task ReadAsync(CancellationToken cancellationToken = default) + { + var request = new ReadRequest { SessionId = _sessionId }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.plan.read", [request], cancellationToken); + } + + /// Calls "session.plan.update". + public async Task UpdateAsync(string content, CancellationToken cancellationToken = default) + { + var request = new UpdateRequest { SessionId = _sessionId, Content = content }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.plan.update", [request], cancellationToken); + } + + /// Calls "session.plan.delete". + public async Task DeleteAsync(CancellationToken cancellationToken = default) + { + var request = new DeleteRequest { SessionId = _sessionId }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.plan.delete", [request], cancellationToken); + } +} + +public class WorkspaceApi +{ + private readonly JsonRpc _rpc; + private readonly string _sessionId; + + internal WorkspaceApi(JsonRpc rpc, string sessionId) + { + _rpc = rpc; + _sessionId = sessionId; + } + + /// Calls "session.workspace.listFiles". + public async Task ListFilesAsync(CancellationToken cancellationToken = default) + { + var request = new ListFilesRequest { SessionId = _sessionId }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.workspace.listFiles", [request], cancellationToken); + } + + /// Calls "session.workspace.readFile". + public async Task ReadFileAsync(string path, CancellationToken cancellationToken = default) + { + var request = new ReadFileRequest { SessionId = _sessionId, Path = path }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.workspace.readFile", [request], cancellationToken); + } + + /// Calls "session.workspace.createFile". + public async Task CreateFileAsync(string path, string content, CancellationToken cancellationToken = default) + { + var request = new CreateFileRequest { SessionId = _sessionId, Path = path, Content = content }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.workspace.createFile", [request], cancellationToken); + } +} + +public class FleetApi +{ + private readonly JsonRpc _rpc; + private readonly string _sessionId; + + internal FleetApi(JsonRpc rpc, string sessionId) + { + _rpc = rpc; + _sessionId = sessionId; + } + + /// Calls "session.fleet.start". + public async Task StartAsync(string? prompt, CancellationToken cancellationToken = default) + { + var request = new StartRequest { SessionId = _sessionId, Prompt = prompt }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.fleet.start", [request], cancellationToken); + } +} + [JsonSourceGenerationOptions( JsonSerializerDefaults.Web, AllowOutOfOrderMetadataProperties = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] [JsonSerializable(typeof(AccountGetQuotaResult))] [JsonSerializable(typeof(AccountGetQuotaResultQuotaSnapshotsValue))] +[JsonSerializable(typeof(CreateFileRequest))] +[JsonSerializable(typeof(DeleteRequest))] [JsonSerializable(typeof(GetCurrentRequest))] +[JsonSerializable(typeof(GetRequest))] +[JsonSerializable(typeof(ListFilesRequest))] [JsonSerializable(typeof(ListRequest))] [JsonSerializable(typeof(Model))] [JsonSerializable(typeof(ModelBilling))] @@ -357,9 +614,23 @@ public async Task SwitchToAsync(string modelId, Canc [JsonSerializable(typeof(ModelsListResult))] [JsonSerializable(typeof(PingRequest))] [JsonSerializable(typeof(PingResult))] +[JsonSerializable(typeof(ReadFileRequest))] +[JsonSerializable(typeof(ReadRequest))] +[JsonSerializable(typeof(SessionFleetStartResult))] +[JsonSerializable(typeof(SessionModeGetResult))] +[JsonSerializable(typeof(SessionModeSetResult))] [JsonSerializable(typeof(SessionModelGetCurrentResult))] [JsonSerializable(typeof(SessionModelSwitchToResult))] +[JsonSerializable(typeof(SessionPlanDeleteResult))] +[JsonSerializable(typeof(SessionPlanReadResult))] +[JsonSerializable(typeof(SessionPlanUpdateResult))] +[JsonSerializable(typeof(SessionWorkspaceCreateFileResult))] +[JsonSerializable(typeof(SessionWorkspaceListFilesResult))] +[JsonSerializable(typeof(SessionWorkspaceReadFileResult))] +[JsonSerializable(typeof(SetRequest))] +[JsonSerializable(typeof(StartRequest))] [JsonSerializable(typeof(SwitchToRequest))] [JsonSerializable(typeof(Tool))] [JsonSerializable(typeof(ToolsListResult))] +[JsonSerializable(typeof(UpdateRequest))] internal partial class RpcJsonContext : JsonSerializerContext; \ No newline at end of file diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index 52cbdbc7..c2549803 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -35,7 +35,9 @@ namespace GitHub.Copilot.SDK; [JsonDerivedType(typeof(SessionHandoffEvent), "session.handoff")] [JsonDerivedType(typeof(SessionIdleEvent), "session.idle")] [JsonDerivedType(typeof(SessionInfoEvent), "session.info")] +[JsonDerivedType(typeof(SessionModeChangedEvent), "session.mode_changed")] [JsonDerivedType(typeof(SessionModelChangeEvent), "session.model_change")] +[JsonDerivedType(typeof(SessionPlanChangedEvent), "session.plan_changed")] [JsonDerivedType(typeof(SessionResumeEvent), "session.resume")] [JsonDerivedType(typeof(SessionShutdownEvent), "session.shutdown")] [JsonDerivedType(typeof(SessionSnapshotRewindEvent), "session.snapshot_rewind")] @@ -44,6 +46,7 @@ namespace GitHub.Copilot.SDK; [JsonDerivedType(typeof(SessionTruncationEvent), "session.truncation")] [JsonDerivedType(typeof(SessionUsageInfoEvent), "session.usage_info")] [JsonDerivedType(typeof(SessionWarningEvent), "session.warning")] +[JsonDerivedType(typeof(SessionWorkspaceFileChangedEvent), "session.workspace_file_changed")] [JsonDerivedType(typeof(SkillInvokedEvent), "skill.invoked")] [JsonDerivedType(typeof(SubagentCompletedEvent), "subagent.completed")] [JsonDerivedType(typeof(SubagentFailedEvent), "subagent.failed")] @@ -180,6 +183,42 @@ public partial class SessionModelChangeEvent : SessionEvent public required SessionModelChangeData Data { get; set; } } +/// +/// Event: session.mode_changed +/// +public partial class SessionModeChangedEvent : SessionEvent +{ + [JsonIgnore] + public override string Type => "session.mode_changed"; + + [JsonPropertyName("data")] + public required SessionModeChangedData Data { get; set; } +} + +/// +/// Event: session.plan_changed +/// +public partial class SessionPlanChangedEvent : SessionEvent +{ + [JsonIgnore] + public override string Type => "session.plan_changed"; + + [JsonPropertyName("data")] + public required SessionPlanChangedData Data { get; set; } +} + +/// +/// Event: session.workspace_file_changed +/// +public partial class SessionWorkspaceFileChangedEvent : SessionEvent +{ + [JsonIgnore] + public override string Type => "session.workspace_file_changed"; + + [JsonPropertyName("data")] + public required SessionWorkspaceFileChangedData Data { get; set; } +} + /// /// Event: session.handoff /// @@ -662,6 +701,30 @@ public partial class SessionModelChangeData public required string NewModel { get; set; } } +public partial class SessionModeChangedData +{ + [JsonPropertyName("previousMode")] + public required string PreviousMode { get; set; } + + [JsonPropertyName("newMode")] + public required string NewMode { get; set; } +} + +public partial class SessionPlanChangedData +{ + [JsonPropertyName("operation")] + public required SessionPlanChangedDataOperation Operation { get; set; } +} + +public partial class SessionWorkspaceFileChangedData +{ + [JsonPropertyName("path")] + public required string Path { get; set; } + + [JsonPropertyName("operation")] + public required SessionWorkspaceFileChangedDataOperation Operation { get; set; } +} + public partial class SessionHandoffData { [JsonPropertyName("handoffTime")] @@ -1577,6 +1640,26 @@ public partial class SystemMessageDataMetadata public Dictionary? Variables { get; set; } } +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum SessionPlanChangedDataOperation +{ + [JsonStringEnumMemberName("create")] + Create, + [JsonStringEnumMemberName("update")] + Update, + [JsonStringEnumMemberName("delete")] + Delete, +} + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum SessionWorkspaceFileChangedDataOperation +{ + [JsonStringEnumMemberName("create")] + Create, + [JsonStringEnumMemberName("update")] + Update, +} + [JsonConverter(typeof(JsonStringEnumConverter))] public enum SessionHandoffDataSourceType { @@ -1683,8 +1766,12 @@ public enum SystemMessageDataRole [JsonSerializable(typeof(SessionIdleEvent))] [JsonSerializable(typeof(SessionInfoData))] [JsonSerializable(typeof(SessionInfoEvent))] +[JsonSerializable(typeof(SessionModeChangedData))] +[JsonSerializable(typeof(SessionModeChangedEvent))] [JsonSerializable(typeof(SessionModelChangeData))] [JsonSerializable(typeof(SessionModelChangeEvent))] +[JsonSerializable(typeof(SessionPlanChangedData))] +[JsonSerializable(typeof(SessionPlanChangedEvent))] [JsonSerializable(typeof(SessionResumeData))] [JsonSerializable(typeof(SessionResumeDataContext))] [JsonSerializable(typeof(SessionResumeEvent))] @@ -1704,6 +1791,8 @@ public enum SystemMessageDataRole [JsonSerializable(typeof(SessionUsageInfoEvent))] [JsonSerializable(typeof(SessionWarningData))] [JsonSerializable(typeof(SessionWarningEvent))] +[JsonSerializable(typeof(SessionWorkspaceFileChangedData))] +[JsonSerializable(typeof(SessionWorkspaceFileChangedEvent))] [JsonSerializable(typeof(SkillInvokedData))] [JsonSerializable(typeof(SkillInvokedEvent))] [JsonSerializable(typeof(SubagentCompletedData))] diff --git a/go/generated_session_events.go b/go/generated_session_events.go index 1980fc69..c11a43c5 100644 --- a/go/generated_session_events.go +++ b/go/generated_session_events.go @@ -35,25 +35,30 @@ type SessionEvent struct { } type Data struct { - Context *ContextUnion `json:"context"` - CopilotVersion *string `json:"copilotVersion,omitempty"` - Producer *string `json:"producer,omitempty"` - SelectedModel *string `json:"selectedModel,omitempty"` - SessionID *string `json:"sessionId,omitempty"` - StartTime *time.Time `json:"startTime,omitempty"` - Version *float64 `json:"version,omitempty"` - EventCount *float64 `json:"eventCount,omitempty"` - ResumeTime *time.Time `json:"resumeTime,omitempty"` - ErrorType *string `json:"errorType,omitempty"` - Message *string `json:"message,omitempty"` - ProviderCallID *string `json:"providerCallId,omitempty"` - Stack *string `json:"stack,omitempty"` - StatusCode *int64 `json:"statusCode,omitempty"` - Title *string `json:"title,omitempty"` - InfoType *string `json:"infoType,omitempty"` - WarningType *string `json:"warningType,omitempty"` - NewModel *string `json:"newModel,omitempty"` - PreviousModel *string `json:"previousModel,omitempty"` + Context *ContextUnion `json:"context"` + CopilotVersion *string `json:"copilotVersion,omitempty"` + Producer *string `json:"producer,omitempty"` + SelectedModel *string `json:"selectedModel,omitempty"` + SessionID *string `json:"sessionId,omitempty"` + StartTime *time.Time `json:"startTime,omitempty"` + Version *float64 `json:"version,omitempty"` + EventCount *float64 `json:"eventCount,omitempty"` + ResumeTime *time.Time `json:"resumeTime,omitempty"` + ErrorType *string `json:"errorType,omitempty"` + Message *string `json:"message,omitempty"` + ProviderCallID *string `json:"providerCallId,omitempty"` + Stack *string `json:"stack,omitempty"` + StatusCode *int64 `json:"statusCode,omitempty"` + Title *string `json:"title,omitempty"` + InfoType *string `json:"infoType,omitempty"` + WarningType *string `json:"warningType,omitempty"` + NewModel *string `json:"newModel,omitempty"` + PreviousModel *string `json:"previousModel,omitempty"` + NewMode *string `json:"newMode,omitempty"` + PreviousMode *string `json:"previousMode,omitempty"` + Operation *Operation `json:"operation,omitempty"` + // Relative path within the workspace files directory + Path *string `json:"path,omitempty"` HandoffTime *time.Time `json:"handoffTime,omitempty"` RemoteSessionID *string `json:"remoteSessionId,omitempty"` Repository *RepositoryUnion `json:"repository"` @@ -134,7 +139,6 @@ type Data struct { ToolTelemetry map[string]interface{} `json:"toolTelemetry,omitempty"` AllowedTools []string `json:"allowedTools,omitempty"` Name *string `json:"name,omitempty"` - Path *string `json:"path,omitempty"` AgentDescription *string `json:"agentDescription,omitempty"` AgentDisplayName *string `json:"agentDisplayName,omitempty"` AgentName *string `json:"agentName,omitempty"` @@ -301,6 +305,14 @@ const ( Selection AttachmentType = "selection" ) +type Operation string + +const ( + Create Operation = "create" + Delete Operation = "delete" + Update Operation = "update" +) + type Theme string const ( @@ -350,46 +362,49 @@ const ( type SessionEventType string const ( - Abort SessionEventType = "abort" - AssistantIntent SessionEventType = "assistant.intent" - AssistantMessage SessionEventType = "assistant.message" - AssistantMessageDelta SessionEventType = "assistant.message_delta" - AssistantReasoning SessionEventType = "assistant.reasoning" - AssistantReasoningDelta SessionEventType = "assistant.reasoning_delta" - AssistantTurnEnd SessionEventType = "assistant.turn_end" - AssistantTurnStart SessionEventType = "assistant.turn_start" - AssistantUsage SessionEventType = "assistant.usage" - HookEnd SessionEventType = "hook.end" - HookStart SessionEventType = "hook.start" - PendingMessagesModified SessionEventType = "pending_messages.modified" - SessionCompactionComplete SessionEventType = "session.compaction_complete" - SessionCompactionStart SessionEventType = "session.compaction_start" - SessionContextChanged SessionEventType = "session.context_changed" - SessionError SessionEventType = "session.error" - SessionHandoff SessionEventType = "session.handoff" - SessionIdle SessionEventType = "session.idle" - SessionInfo SessionEventType = "session.info" - SessionModelChange SessionEventType = "session.model_change" - SessionResume SessionEventType = "session.resume" - SessionShutdown SessionEventType = "session.shutdown" - SessionSnapshotRewind SessionEventType = "session.snapshot_rewind" - SessionStart SessionEventType = "session.start" - SessionTitleChanged SessionEventType = "session.title_changed" - SessionTruncation SessionEventType = "session.truncation" - SessionUsageInfo SessionEventType = "session.usage_info" - SessionWarning SessionEventType = "session.warning" - SkillInvoked SessionEventType = "skill.invoked" - SubagentCompleted SessionEventType = "subagent.completed" - SubagentFailed SessionEventType = "subagent.failed" - SubagentSelected SessionEventType = "subagent.selected" - SubagentStarted SessionEventType = "subagent.started" - SystemMessage SessionEventType = "system.message" - ToolExecutionComplete SessionEventType = "tool.execution_complete" - ToolExecutionPartialResult SessionEventType = "tool.execution_partial_result" - ToolExecutionProgress SessionEventType = "tool.execution_progress" - ToolExecutionStart SessionEventType = "tool.execution_start" - ToolUserRequested SessionEventType = "tool.user_requested" - UserMessage SessionEventType = "user.message" + Abort SessionEventType = "abort" + AssistantIntent SessionEventType = "assistant.intent" + AssistantMessage SessionEventType = "assistant.message" + AssistantMessageDelta SessionEventType = "assistant.message_delta" + AssistantReasoning SessionEventType = "assistant.reasoning" + AssistantReasoningDelta SessionEventType = "assistant.reasoning_delta" + AssistantTurnEnd SessionEventType = "assistant.turn_end" + AssistantTurnStart SessionEventType = "assistant.turn_start" + AssistantUsage SessionEventType = "assistant.usage" + HookEnd SessionEventType = "hook.end" + HookStart SessionEventType = "hook.start" + PendingMessagesModified SessionEventType = "pending_messages.modified" + SessionCompactionComplete SessionEventType = "session.compaction_complete" + SessionCompactionStart SessionEventType = "session.compaction_start" + SessionContextChanged SessionEventType = "session.context_changed" + SessionError SessionEventType = "session.error" + SessionHandoff SessionEventType = "session.handoff" + SessionIdle SessionEventType = "session.idle" + SessionInfo SessionEventType = "session.info" + SessionModeChanged SessionEventType = "session.mode_changed" + SessionModelChange SessionEventType = "session.model_change" + SessionPlanChanged SessionEventType = "session.plan_changed" + SessionResume SessionEventType = "session.resume" + SessionShutdown SessionEventType = "session.shutdown" + SessionSnapshotRewind SessionEventType = "session.snapshot_rewind" + SessionStart SessionEventType = "session.start" + SessionTitleChanged SessionEventType = "session.title_changed" + SessionTruncation SessionEventType = "session.truncation" + SessionUsageInfo SessionEventType = "session.usage_info" + SessionWarning SessionEventType = "session.warning" + SessionWorkspaceFileChanged SessionEventType = "session.workspace_file_changed" + SkillInvoked SessionEventType = "skill.invoked" + SubagentCompleted SessionEventType = "subagent.completed" + SubagentFailed SessionEventType = "subagent.failed" + SubagentSelected SessionEventType = "subagent.selected" + SubagentStarted SessionEventType = "subagent.started" + SystemMessage SessionEventType = "system.message" + ToolExecutionComplete SessionEventType = "tool.execution_complete" + ToolExecutionPartialResult SessionEventType = "tool.execution_partial_result" + ToolExecutionProgress SessionEventType = "tool.execution_progress" + ToolExecutionStart SessionEventType = "tool.execution_start" + ToolUserRequested SessionEventType = "tool.user_requested" + UserMessage SessionEventType = "user.message" ) type ContextUnion struct { diff --git a/go/rpc/generated_rpc.go b/go/rpc/generated_rpc.go index bca3859e..ec27c4d8 100644 --- a/go/rpc/generated_rpc.go +++ b/go/rpc/generated_rpc.go @@ -132,6 +132,87 @@ type SessionModelSwitchToParams struct { ModelID string `json:"modelId"` } +type SessionModeGetResult struct { + // The current agent mode. + Mode Mode `json:"mode"` +} + +type SessionModeSetResult struct { + // The agent mode after switching. + Mode Mode `json:"mode"` +} + +type SessionModeSetParams struct { + // The mode to switch to. Valid values: "interactive", "plan", "autopilot". + Mode Mode `json:"mode"` +} + +type SessionPlanReadResult struct { + // The content of plan.md, or null if it does not exist + Content *string `json:"content"` + // Whether plan.md exists in the workspace + Exists bool `json:"exists"` +} + +type SessionPlanUpdateResult struct { +} + +type SessionPlanUpdateParams struct { + // The new content for plan.md + Content string `json:"content"` +} + +type SessionPlanDeleteResult struct { +} + +type SessionWorkspaceListFilesResult struct { + // Relative file paths in the workspace files directory + Files []string `json:"files"` +} + +type SessionWorkspaceReadFileResult struct { + // File content as a UTF-8 string + Content string `json:"content"` +} + +type SessionWorkspaceReadFileParams struct { + // Relative path within the workspace files directory + Path string `json:"path"` +} + +type SessionWorkspaceCreateFileResult struct { +} + +type SessionWorkspaceCreateFileParams struct { + // File content to write as a UTF-8 string + Content string `json:"content"` + // Relative path within the workspace files directory + Path string `json:"path"` +} + +type SessionFleetStartResult struct { + // Whether fleet mode was successfully activated + Started bool `json:"started"` +} + +type SessionFleetStartParams struct { + // Optional user prompt to combine with fleet instructions + Prompt *string `json:"prompt,omitempty"` +} + +// The current agent mode. +// +// The agent mode after switching. +// +// The mode to switch to. Valid values: "interactive", "plan", "autopilot". +type Mode string + +const ( + Autopilot Mode = "autopilot" + Interactive Mode = "interactive" + Plan Mode = "plan" +) + type ModelsRpcApi struct{ client *jsonrpc2.Client } func (a *ModelsRpcApi) List(ctx context.Context) (*ModelsListResult, error) { @@ -236,15 +317,176 @@ func (a *ModelRpcApi) SwitchTo(ctx context.Context, params *SessionModelSwitchTo return &result, nil } +type ModeRpcApi struct { + client *jsonrpc2.Client + sessionID string +} + +func (a *ModeRpcApi) Get(ctx context.Context) (*SessionModeGetResult, error) { + req := map[string]interface{}{"sessionId": a.sessionID} + raw, err := a.client.Request("session.mode.get", req) + if err != nil { + return nil, err + } + var result SessionModeGetResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +func (a *ModeRpcApi) Set(ctx context.Context, params *SessionModeSetParams) (*SessionModeSetResult, error) { + req := map[string]interface{}{"sessionId": a.sessionID} + if params != nil { + req["mode"] = params.Mode + } + raw, err := a.client.Request("session.mode.set", req) + if err != nil { + return nil, err + } + var result SessionModeSetResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +type PlanRpcApi struct { + client *jsonrpc2.Client + sessionID string +} + +func (a *PlanRpcApi) Read(ctx context.Context) (*SessionPlanReadResult, error) { + req := map[string]interface{}{"sessionId": a.sessionID} + raw, err := a.client.Request("session.plan.read", req) + if err != nil { + return nil, err + } + var result SessionPlanReadResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +func (a *PlanRpcApi) Update(ctx context.Context, params *SessionPlanUpdateParams) (*SessionPlanUpdateResult, error) { + req := map[string]interface{}{"sessionId": a.sessionID} + if params != nil { + req["content"] = params.Content + } + raw, err := a.client.Request("session.plan.update", req) + if err != nil { + return nil, err + } + var result SessionPlanUpdateResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +func (a *PlanRpcApi) Delete(ctx context.Context) (*SessionPlanDeleteResult, error) { + req := map[string]interface{}{"sessionId": a.sessionID} + raw, err := a.client.Request("session.plan.delete", req) + if err != nil { + return nil, err + } + var result SessionPlanDeleteResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +type WorkspaceRpcApi struct { + client *jsonrpc2.Client + sessionID string +} + +func (a *WorkspaceRpcApi) ListFiles(ctx context.Context) (*SessionWorkspaceListFilesResult, error) { + req := map[string]interface{}{"sessionId": a.sessionID} + raw, err := a.client.Request("session.workspace.listFiles", req) + if err != nil { + return nil, err + } + var result SessionWorkspaceListFilesResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +func (a *WorkspaceRpcApi) ReadFile(ctx context.Context, params *SessionWorkspaceReadFileParams) (*SessionWorkspaceReadFileResult, error) { + req := map[string]interface{}{"sessionId": a.sessionID} + if params != nil { + req["path"] = params.Path + } + raw, err := a.client.Request("session.workspace.readFile", req) + if err != nil { + return nil, err + } + var result SessionWorkspaceReadFileResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +func (a *WorkspaceRpcApi) CreateFile(ctx context.Context, params *SessionWorkspaceCreateFileParams) (*SessionWorkspaceCreateFileResult, error) { + req := map[string]interface{}{"sessionId": a.sessionID} + if params != nil { + req["path"] = params.Path + req["content"] = params.Content + } + raw, err := a.client.Request("session.workspace.createFile", req) + if err != nil { + return nil, err + } + var result SessionWorkspaceCreateFileResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +type FleetRpcApi struct { + client *jsonrpc2.Client + sessionID string +} + +func (a *FleetRpcApi) Start(ctx context.Context, params *SessionFleetStartParams) (*SessionFleetStartResult, error) { + req := map[string]interface{}{"sessionId": a.sessionID} + if params != nil { + req["prompt"] = params.Prompt + } + raw, err := a.client.Request("session.fleet.start", req) + if err != nil { + return nil, err + } + var result SessionFleetStartResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // SessionRpc provides typed session-scoped RPC methods. type SessionRpc struct { client *jsonrpc2.Client sessionID string Model *ModelRpcApi + Mode *ModeRpcApi + Plan *PlanRpcApi + Workspace *WorkspaceRpcApi + Fleet *FleetRpcApi } func NewSessionRpc(client *jsonrpc2.Client, sessionID string) *SessionRpc { return &SessionRpc{client: client, sessionID: sessionID, - Model: &ModelRpcApi{client: client, sessionID: sessionID}, + Model: &ModelRpcApi{client: client, sessionID: sessionID}, + Mode: &ModeRpcApi{client: client, sessionID: sessionID}, + Plan: &PlanRpcApi{client: client, sessionID: sessionID}, + Workspace: &WorkspaceRpcApi{client: client, sessionID: sessionID}, + Fleet: &FleetRpcApi{client: client, sessionID: sessionID}, } } diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index 4bd7de36..12c992bd 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -175,6 +175,145 @@ export interface SessionModelSwitchToParams { modelId: string; } +export interface SessionModeGetResult { + /** + * The current agent mode. + */ + mode: "interactive" | "plan" | "autopilot"; +} + +export interface SessionModeGetParams { + /** + * Target session identifier + */ + sessionId: string; +} + +export interface SessionModeSetResult { + /** + * The agent mode after switching. + */ + mode: "interactive" | "plan" | "autopilot"; +} + +export interface SessionModeSetParams { + /** + * Target session identifier + */ + sessionId: string; + /** + * The mode to switch to. Valid values: "interactive", "plan", "autopilot". + */ + mode: "interactive" | "plan" | "autopilot"; +} + +export interface SessionPlanReadResult { + /** + * Whether plan.md exists in the workspace + */ + exists: boolean; + /** + * The content of plan.md, or null if it does not exist + */ + content: string | null; +} + +export interface SessionPlanReadParams { + /** + * Target session identifier + */ + sessionId: string; +} + +export interface SessionPlanUpdateResult {} + +export interface SessionPlanUpdateParams { + /** + * Target session identifier + */ + sessionId: string; + /** + * The new content for plan.md + */ + content: string; +} + +export interface SessionPlanDeleteResult {} + +export interface SessionPlanDeleteParams { + /** + * Target session identifier + */ + sessionId: string; +} + +export interface SessionWorkspaceListFilesResult { + /** + * Relative file paths in the workspace files directory + */ + files: string[]; +} + +export interface SessionWorkspaceListFilesParams { + /** + * Target session identifier + */ + sessionId: string; +} + +export interface SessionWorkspaceReadFileResult { + /** + * File content as a UTF-8 string + */ + content: string; +} + +export interface SessionWorkspaceReadFileParams { + /** + * Target session identifier + */ + sessionId: string; + /** + * Relative path within the workspace files directory + */ + path: string; +} + +export interface SessionWorkspaceCreateFileResult {} + +export interface SessionWorkspaceCreateFileParams { + /** + * Target session identifier + */ + sessionId: string; + /** + * Relative path within the workspace files directory + */ + path: string; + /** + * File content to write as a UTF-8 string + */ + content: string; +} + +export interface SessionFleetStartResult { + /** + * Whether fleet mode was successfully activated + */ + started: boolean; +} + +export interface SessionFleetStartParams { + /** + * Target session identifier + */ + sessionId: string; + /** + * Optional user prompt to combine with fleet instructions + */ + prompt?: string; +} + /** Create typed server-scoped RPC methods (no session required). */ export function createServerRpc(connection: MessageConnection) { return { @@ -204,5 +343,31 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin switchTo: async (params: Omit): Promise => connection.sendRequest("session.model.switchTo", { sessionId, ...params }), }, + mode: { + get: async (): Promise => + connection.sendRequest("session.mode.get", { sessionId }), + set: async (params: Omit): Promise => + connection.sendRequest("session.mode.set", { sessionId, ...params }), + }, + plan: { + read: async (): Promise => + connection.sendRequest("session.plan.read", { sessionId }), + update: async (params: Omit): Promise => + connection.sendRequest("session.plan.update", { sessionId, ...params }), + delete: async (): Promise => + connection.sendRequest("session.plan.delete", { sessionId }), + }, + workspace: { + listFiles: async (): Promise => + connection.sendRequest("session.workspace.listFiles", { sessionId }), + readFile: async (params: Omit): Promise => + connection.sendRequest("session.workspace.readFile", { sessionId, ...params }), + createFile: async (params: Omit): Promise => + connection.sendRequest("session.workspace.createFile", { sessionId, ...params }), + }, + fleet: { + start: async (params: Omit): Promise => + connection.sendRequest("session.fleet.start", { sessionId, ...params }), + }, }; } diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 940ee212..032a1723 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -107,6 +107,41 @@ export type SessionEvent = newModel: string; }; } + | { + id: string; + timestamp: string; + parentId: string | null; + ephemeral?: boolean; + type: "session.mode_changed"; + data: { + previousMode: string; + newMode: string; + }; + } + | { + id: string; + timestamp: string; + parentId: string | null; + ephemeral?: boolean; + type: "session.plan_changed"; + data: { + operation: "create" | "update" | "delete"; + }; + } + | { + id: string; + timestamp: string; + parentId: string | null; + ephemeral?: boolean; + type: "session.workspace_file_changed"; + data: { + /** + * Relative path within the workspace files directory + */ + path: string; + operation: "create" | "update"; + }; + } | { id: string; timestamp: string; diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index 14a1ec7c..5707e427 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -11,9 +11,11 @@ from dataclasses import dataclass from typing import Any, Optional, List, Dict, TypeVar, Type, cast, Callable +from enum import Enum T = TypeVar("T") +EnumT = TypeVar("EnumT", bound=Enum) def from_str(x: Any) -> str: @@ -65,6 +67,11 @@ def from_dict(f: Callable[[Any], T], x: Any) -> Dict[str, T]: return { k: f(v) for (k, v) in x.items() } +def to_enum(c: Type[EnumT], x: Any) -> EnumT: + assert isinstance(x, c) + return x.value + + @dataclass class PingResult: message: str @@ -471,6 +478,258 @@ def to_dict(self) -> dict: return result +class Mode(Enum): + """The current agent mode. + + The agent mode after switching. + + The mode to switch to. Valid values: "interactive", "plan", "autopilot". + """ + AUTOPILOT = "autopilot" + INTERACTIVE = "interactive" + PLAN = "plan" + + +@dataclass +class SessionModeGetResult: + mode: Mode + """The current agent mode.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionModeGetResult': + assert isinstance(obj, dict) + mode = Mode(obj.get("mode")) + return SessionModeGetResult(mode) + + def to_dict(self) -> dict: + result: dict = {} + result["mode"] = to_enum(Mode, self.mode) + return result + + +@dataclass +class SessionModeSetResult: + mode: Mode + """The agent mode after switching.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionModeSetResult': + assert isinstance(obj, dict) + mode = Mode(obj.get("mode")) + return SessionModeSetResult(mode) + + def to_dict(self) -> dict: + result: dict = {} + result["mode"] = to_enum(Mode, self.mode) + return result + + +@dataclass +class SessionModeSetParams: + mode: Mode + """The mode to switch to. Valid values: "interactive", "plan", "autopilot".""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionModeSetParams': + assert isinstance(obj, dict) + mode = Mode(obj.get("mode")) + return SessionModeSetParams(mode) + + def to_dict(self) -> dict: + result: dict = {} + result["mode"] = to_enum(Mode, self.mode) + return result + + +@dataclass +class SessionPlanReadResult: + exists: bool + """Whether plan.md exists in the workspace""" + + content: Optional[str] = None + """The content of plan.md, or null if it does not exist""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionPlanReadResult': + assert isinstance(obj, dict) + exists = from_bool(obj.get("exists")) + content = from_union([from_none, from_str], obj.get("content")) + return SessionPlanReadResult(exists, content) + + def to_dict(self) -> dict: + result: dict = {} + result["exists"] = from_bool(self.exists) + result["content"] = from_union([from_none, from_str], self.content) + return result + + +@dataclass +class SessionPlanUpdateResult: + pass + + @staticmethod + def from_dict(obj: Any) -> 'SessionPlanUpdateResult': + assert isinstance(obj, dict) + return SessionPlanUpdateResult() + + def to_dict(self) -> dict: + result: dict = {} + return result + + +@dataclass +class SessionPlanUpdateParams: + content: str + """The new content for plan.md""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionPlanUpdateParams': + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + return SessionPlanUpdateParams(content) + + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + return result + + +@dataclass +class SessionPlanDeleteResult: + pass + + @staticmethod + def from_dict(obj: Any) -> 'SessionPlanDeleteResult': + assert isinstance(obj, dict) + return SessionPlanDeleteResult() + + def to_dict(self) -> dict: + result: dict = {} + return result + + +@dataclass +class SessionWorkspaceListFilesResult: + files: List[str] + """Relative file paths in the workspace files directory""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionWorkspaceListFilesResult': + assert isinstance(obj, dict) + files = from_list(from_str, obj.get("files")) + return SessionWorkspaceListFilesResult(files) + + def to_dict(self) -> dict: + result: dict = {} + result["files"] = from_list(from_str, self.files) + return result + + +@dataclass +class SessionWorkspaceReadFileResult: + content: str + """File content as a UTF-8 string""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionWorkspaceReadFileResult': + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + return SessionWorkspaceReadFileResult(content) + + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + return result + + +@dataclass +class SessionWorkspaceReadFileParams: + path: str + """Relative path within the workspace files directory""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionWorkspaceReadFileParams': + assert isinstance(obj, dict) + path = from_str(obj.get("path")) + return SessionWorkspaceReadFileParams(path) + + def to_dict(self) -> dict: + result: dict = {} + result["path"] = from_str(self.path) + return result + + +@dataclass +class SessionWorkspaceCreateFileResult: + pass + + @staticmethod + def from_dict(obj: Any) -> 'SessionWorkspaceCreateFileResult': + assert isinstance(obj, dict) + return SessionWorkspaceCreateFileResult() + + def to_dict(self) -> dict: + result: dict = {} + return result + + +@dataclass +class SessionWorkspaceCreateFileParams: + content: str + """File content to write as a UTF-8 string""" + + path: str + """Relative path within the workspace files directory""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionWorkspaceCreateFileParams': + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + path = from_str(obj.get("path")) + return SessionWorkspaceCreateFileParams(content, path) + + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + result["path"] = from_str(self.path) + return result + + +@dataclass +class SessionFleetStartResult: + started: bool + """Whether fleet mode was successfully activated""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFleetStartResult': + assert isinstance(obj, dict) + started = from_bool(obj.get("started")) + return SessionFleetStartResult(started) + + def to_dict(self) -> dict: + result: dict = {} + result["started"] = from_bool(self.started) + return result + + +@dataclass +class SessionFleetStartParams: + prompt: Optional[str] = None + """Optional user prompt to combine with fleet instructions""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFleetStartParams': + assert isinstance(obj, dict) + prompt = from_union([from_str, from_none], obj.get("prompt")) + return SessionFleetStartParams(prompt) + + def to_dict(self) -> dict: + result: dict = {} + if self.prompt is not None: + result["prompt"] = from_union([from_str, from_none], self.prompt) + return result + + def ping_result_from_dict(s: Any) -> PingResult: return PingResult.from_dict(s) @@ -543,6 +802,118 @@ def session_model_switch_to_params_to_dict(x: SessionModelSwitchToParams) -> Any return to_class(SessionModelSwitchToParams, x) +def session_mode_get_result_from_dict(s: Any) -> SessionModeGetResult: + return SessionModeGetResult.from_dict(s) + + +def session_mode_get_result_to_dict(x: SessionModeGetResult) -> Any: + return to_class(SessionModeGetResult, x) + + +def session_mode_set_result_from_dict(s: Any) -> SessionModeSetResult: + return SessionModeSetResult.from_dict(s) + + +def session_mode_set_result_to_dict(x: SessionModeSetResult) -> Any: + return to_class(SessionModeSetResult, x) + + +def session_mode_set_params_from_dict(s: Any) -> SessionModeSetParams: + return SessionModeSetParams.from_dict(s) + + +def session_mode_set_params_to_dict(x: SessionModeSetParams) -> Any: + return to_class(SessionModeSetParams, x) + + +def session_plan_read_result_from_dict(s: Any) -> SessionPlanReadResult: + return SessionPlanReadResult.from_dict(s) + + +def session_plan_read_result_to_dict(x: SessionPlanReadResult) -> Any: + return to_class(SessionPlanReadResult, x) + + +def session_plan_update_result_from_dict(s: Any) -> SessionPlanUpdateResult: + return SessionPlanUpdateResult.from_dict(s) + + +def session_plan_update_result_to_dict(x: SessionPlanUpdateResult) -> Any: + return to_class(SessionPlanUpdateResult, x) + + +def session_plan_update_params_from_dict(s: Any) -> SessionPlanUpdateParams: + return SessionPlanUpdateParams.from_dict(s) + + +def session_plan_update_params_to_dict(x: SessionPlanUpdateParams) -> Any: + return to_class(SessionPlanUpdateParams, x) + + +def session_plan_delete_result_from_dict(s: Any) -> SessionPlanDeleteResult: + return SessionPlanDeleteResult.from_dict(s) + + +def session_plan_delete_result_to_dict(x: SessionPlanDeleteResult) -> Any: + return to_class(SessionPlanDeleteResult, x) + + +def session_workspace_list_files_result_from_dict(s: Any) -> SessionWorkspaceListFilesResult: + return SessionWorkspaceListFilesResult.from_dict(s) + + +def session_workspace_list_files_result_to_dict(x: SessionWorkspaceListFilesResult) -> Any: + return to_class(SessionWorkspaceListFilesResult, x) + + +def session_workspace_read_file_result_from_dict(s: Any) -> SessionWorkspaceReadFileResult: + return SessionWorkspaceReadFileResult.from_dict(s) + + +def session_workspace_read_file_result_to_dict(x: SessionWorkspaceReadFileResult) -> Any: + return to_class(SessionWorkspaceReadFileResult, x) + + +def session_workspace_read_file_params_from_dict(s: Any) -> SessionWorkspaceReadFileParams: + return SessionWorkspaceReadFileParams.from_dict(s) + + +def session_workspace_read_file_params_to_dict(x: SessionWorkspaceReadFileParams) -> Any: + return to_class(SessionWorkspaceReadFileParams, x) + + +def session_workspace_create_file_result_from_dict(s: Any) -> SessionWorkspaceCreateFileResult: + return SessionWorkspaceCreateFileResult.from_dict(s) + + +def session_workspace_create_file_result_to_dict(x: SessionWorkspaceCreateFileResult) -> Any: + return to_class(SessionWorkspaceCreateFileResult, x) + + +def session_workspace_create_file_params_from_dict(s: Any) -> SessionWorkspaceCreateFileParams: + return SessionWorkspaceCreateFileParams.from_dict(s) + + +def session_workspace_create_file_params_to_dict(x: SessionWorkspaceCreateFileParams) -> Any: + return to_class(SessionWorkspaceCreateFileParams, x) + + +def session_fleet_start_result_from_dict(s: Any) -> SessionFleetStartResult: + return SessionFleetStartResult.from_dict(s) + + +def session_fleet_start_result_to_dict(x: SessionFleetStartResult) -> Any: + return to_class(SessionFleetStartResult, x) + + +def session_fleet_start_params_from_dict(s: Any) -> SessionFleetStartParams: + return SessionFleetStartParams.from_dict(s) + + +def session_fleet_start_params_to_dict(x: SessionFleetStartParams) -> Any: + return to_class(SessionFleetStartParams, x) + + class ModelsApi: def __init__(self, client: "JsonRpcClient"): self._client = client @@ -595,10 +966,75 @@ async def switch_to(self, params: SessionModelSwitchToParams) -> SessionModelSwi return SessionModelSwitchToResult.from_dict(await self._client.request("session.model.switchTo", params_dict)) +class ModeApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def get(self) -> SessionModeGetResult: + return SessionModeGetResult.from_dict(await self._client.request("session.mode.get", {"sessionId": self._session_id})) + + async def set(self, params: SessionModeSetParams) -> SessionModeSetResult: + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return SessionModeSetResult.from_dict(await self._client.request("session.mode.set", params_dict)) + + +class PlanApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def read(self) -> SessionPlanReadResult: + return SessionPlanReadResult.from_dict(await self._client.request("session.plan.read", {"sessionId": self._session_id})) + + async def update(self, params: SessionPlanUpdateParams) -> SessionPlanUpdateResult: + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return SessionPlanUpdateResult.from_dict(await self._client.request("session.plan.update", params_dict)) + + async def delete(self) -> SessionPlanDeleteResult: + return SessionPlanDeleteResult.from_dict(await self._client.request("session.plan.delete", {"sessionId": self._session_id})) + + +class WorkspaceApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def list_files(self) -> SessionWorkspaceListFilesResult: + return SessionWorkspaceListFilesResult.from_dict(await self._client.request("session.workspace.listFiles", {"sessionId": self._session_id})) + + async def read_file(self, params: SessionWorkspaceReadFileParams) -> SessionWorkspaceReadFileResult: + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return SessionWorkspaceReadFileResult.from_dict(await self._client.request("session.workspace.readFile", params_dict)) + + async def create_file(self, params: SessionWorkspaceCreateFileParams) -> SessionWorkspaceCreateFileResult: + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return SessionWorkspaceCreateFileResult.from_dict(await self._client.request("session.workspace.createFile", params_dict)) + + +class FleetApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def start(self, params: SessionFleetStartParams) -> SessionFleetStartResult: + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return SessionFleetStartResult.from_dict(await self._client.request("session.fleet.start", params_dict)) + + class SessionRpc: """Typed session-scoped RPC methods.""" def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id self.model = ModelApi(client, session_id) + self.mode = ModeApi(client, session_id) + self.plan = PlanApi(client, session_id) + self.workspace = WorkspaceApi(client, session_id) + self.fleet = FleetApi(client, session_id) diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index a17afb22..0d588058 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -386,6 +386,12 @@ def to_dict(self) -> dict: return result +class Operation(Enum): + CREATE = "create" + DELETE = "delete" + UPDATE = "update" + + @dataclass class QuotaSnapshot: entitlement_requests: float @@ -673,6 +679,12 @@ class Data: warning_type: Optional[str] = None new_model: Optional[str] = None previous_model: Optional[str] = None + new_mode: Optional[str] = None + previous_mode: Optional[str] = None + operation: Optional[Operation] = None + path: Optional[str] = None + """Relative path within the workspace files directory""" + handoff_time: Optional[datetime] = None remote_session_id: Optional[str] = None repository: Optional[Union[RepositoryClass, str]] = None @@ -753,7 +765,6 @@ class Data: tool_telemetry: Optional[Dict[str, Any]] = None allowed_tools: Optional[List[str]] = None name: Optional[str] = None - path: Optional[str] = None agent_description: Optional[str] = None agent_display_name: Optional[str] = None agent_name: Optional[str] = None @@ -787,6 +798,10 @@ def from_dict(obj: Any) -> 'Data': warning_type = from_union([from_str, from_none], obj.get("warningType")) new_model = from_union([from_str, from_none], obj.get("newModel")) previous_model = from_union([from_str, from_none], obj.get("previousModel")) + new_mode = from_union([from_str, from_none], obj.get("newMode")) + previous_mode = from_union([from_str, from_none], obj.get("previousMode")) + operation = from_union([Operation, from_none], obj.get("operation")) + path = from_union([from_str, from_none], obj.get("path")) handoff_time = from_union([from_datetime, from_none], obj.get("handoffTime")) remote_session_id = from_union([from_str, from_none], obj.get("remoteSessionId")) repository = from_union([RepositoryClass.from_dict, from_str, from_none], obj.get("repository")) @@ -867,7 +882,6 @@ def from_dict(obj: Any) -> 'Data': tool_telemetry = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("toolTelemetry")) allowed_tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("allowedTools")) name = from_union([from_str, from_none], obj.get("name")) - path = from_union([from_str, from_none], obj.get("path")) agent_description = from_union([from_str, from_none], obj.get("agentDescription")) agent_display_name = from_union([from_str, from_none], obj.get("agentDisplayName")) agent_name = from_union([from_str, from_none], obj.get("agentName")) @@ -878,7 +892,7 @@ def from_dict(obj: Any) -> 'Data': output = obj.get("output") metadata = from_union([Metadata.from_dict, from_none], obj.get("metadata")) role = from_union([Role, from_none], obj.get("role")) - return Data(context, copilot_version, producer, selected_model, session_id, start_time, version, event_count, resume_time, error_type, message, provider_call_id, stack, status_code, title, info_type, warning_type, new_model, previous_model, handoff_time, remote_session_id, repository, source_type, summary, messages_removed_during_truncation, performed_by, post_truncation_messages_length, post_truncation_tokens_in_messages, pre_truncation_messages_length, pre_truncation_tokens_in_messages, token_limit, tokens_removed_during_truncation, events_removed, up_to_event_id, code_changes, current_model, error_reason, model_metrics, session_start_time, shutdown_type, total_api_duration_ms, total_premium_requests, branch, cwd, git_root, current_tokens, messages_length, checkpoint_number, checkpoint_path, compaction_tokens_used, error, messages_removed, post_compaction_tokens, pre_compaction_messages_length, pre_compaction_tokens, request_id, success, summary_content, tokens_removed, agent_mode, attachments, content, source, transformed_content, turn_id, intent, reasoning_id, delta_content, encrypted_content, message_id, parent_tool_call_id, phase, reasoning_opaque, reasoning_text, tool_requests, total_response_size_bytes, api_call_id, cache_read_tokens, cache_write_tokens, cost, duration, initiator, input_tokens, model, output_tokens, quota_snapshots, reason, arguments, tool_call_id, tool_name, mcp_server_name, mcp_tool_name, partial_output, progress_message, is_user_requested, result, tool_telemetry, allowed_tools, name, path, agent_description, agent_display_name, agent_name, tools, hook_invocation_id, hook_type, input, output, metadata, role) + return Data(context, copilot_version, producer, selected_model, session_id, start_time, version, event_count, resume_time, error_type, message, provider_call_id, stack, status_code, title, info_type, warning_type, new_model, previous_model, new_mode, previous_mode, operation, path, handoff_time, remote_session_id, repository, source_type, summary, messages_removed_during_truncation, performed_by, post_truncation_messages_length, post_truncation_tokens_in_messages, pre_truncation_messages_length, pre_truncation_tokens_in_messages, token_limit, tokens_removed_during_truncation, events_removed, up_to_event_id, code_changes, current_model, error_reason, model_metrics, session_start_time, shutdown_type, total_api_duration_ms, total_premium_requests, branch, cwd, git_root, current_tokens, messages_length, checkpoint_number, checkpoint_path, compaction_tokens_used, error, messages_removed, post_compaction_tokens, pre_compaction_messages_length, pre_compaction_tokens, request_id, success, summary_content, tokens_removed, agent_mode, attachments, content, source, transformed_content, turn_id, intent, reasoning_id, delta_content, encrypted_content, message_id, parent_tool_call_id, phase, reasoning_opaque, reasoning_text, tool_requests, total_response_size_bytes, api_call_id, cache_read_tokens, cache_write_tokens, cost, duration, initiator, input_tokens, model, output_tokens, quota_snapshots, reason, arguments, tool_call_id, tool_name, mcp_server_name, mcp_tool_name, partial_output, progress_message, is_user_requested, result, tool_telemetry, allowed_tools, name, agent_description, agent_display_name, agent_name, tools, hook_invocation_id, hook_type, input, output, metadata, role) def to_dict(self) -> dict: result: dict = {} @@ -920,6 +934,14 @@ def to_dict(self) -> dict: result["newModel"] = from_union([from_str, from_none], self.new_model) if self.previous_model is not None: result["previousModel"] = from_union([from_str, from_none], self.previous_model) + if self.new_mode is not None: + result["newMode"] = from_union([from_str, from_none], self.new_mode) + if self.previous_mode is not None: + result["previousMode"] = from_union([from_str, from_none], self.previous_mode) + if self.operation is not None: + result["operation"] = from_union([lambda x: to_enum(Operation, x), from_none], self.operation) + if self.path is not None: + result["path"] = from_union([from_str, from_none], self.path) if self.handoff_time is not None: result["handoffTime"] = from_union([lambda x: x.isoformat(), from_none], self.handoff_time) if self.remote_session_id is not None: @@ -1080,8 +1102,6 @@ def to_dict(self) -> dict: result["allowedTools"] = from_union([lambda x: from_list(from_str, x), from_none], self.allowed_tools) if self.name is not None: result["name"] = from_union([from_str, from_none], self.name) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) if self.agent_description is not None: result["agentDescription"] = from_union([from_str, from_none], self.agent_description) if self.agent_display_name is not None: @@ -1126,6 +1146,8 @@ class SessionEventType(Enum): SESSION_IDLE = "session.idle" SESSION_INFO = "session.info" SESSION_MODEL_CHANGE = "session.model_change" + SESSION_MODE_CHANGED = "session.mode_changed" + SESSION_PLAN_CHANGED = "session.plan_changed" SESSION_RESUME = "session.resume" SESSION_SHUTDOWN = "session.shutdown" SESSION_SNAPSHOT_REWIND = "session.snapshot_rewind" @@ -1134,6 +1156,7 @@ class SessionEventType(Enum): SESSION_TRUNCATION = "session.truncation" SESSION_USAGE_INFO = "session.usage_info" SESSION_WARNING = "session.warning" + SESSION_WORKSPACE_FILE_CHANGED = "session.workspace_file_changed" SKILL_INVOKED = "skill.invoked" SUBAGENT_COMPLETED = "subagent.completed" SUBAGENT_FAILED = "subagent.failed" From 7292e7ca6659d2e232c863d0113d95cdb105d1ce Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 17 Feb 2026 10:31:33 +0000 Subject: [PATCH 3/6] test: add E2E tests for new session RPC features (mode, plan, workspace) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- nodejs/test/e2e/rpc.test.ts | 76 +++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/nodejs/test/e2e/rpc.test.ts b/nodejs/test/e2e/rpc.test.ts index 99af862f..b7acbaf6 100644 --- a/nodejs/test/e2e/rpc.test.ts +++ b/nodejs/test/e2e/rpc.test.ts @@ -94,4 +94,80 @@ describe("Session RPC", async () => { const after = await session.rpc.model.getCurrent(); expect(after.modelId).toBe("gpt-4.1"); }); + + it("should get and set session mode", async () => { + const session = await client.createSession(); + + // Get initial mode (default should be interactive) + const initial = await session.rpc.mode.get(); + expect(initial.mode).toBe("interactive"); + + // Switch to plan mode + const planResult = await session.rpc.mode.set({ mode: "plan" }); + expect(planResult.mode).toBe("plan"); + + // Verify mode persisted + const afterPlan = await session.rpc.mode.get(); + expect(afterPlan.mode).toBe("plan"); + + // Switch back to interactive + const interactiveResult = await session.rpc.mode.set({ mode: "interactive" }); + expect(interactiveResult.mode).toBe("interactive"); + }); + + it("should read, update, and delete plan", async () => { + const session = await client.createSession(); + + // Initially plan should not exist + const initial = await session.rpc.plan.read(); + expect(initial.exists).toBe(false); + expect(initial.content).toBeNull(); + + // Create/update plan + const planContent = "# Test Plan\n\n- Step 1\n- Step 2"; + await session.rpc.plan.update({ content: planContent }); + + // Verify plan exists and has correct content + const afterUpdate = await session.rpc.plan.read(); + expect(afterUpdate.exists).toBe(true); + expect(afterUpdate.content).toBe(planContent); + + // Delete plan + await session.rpc.plan.delete(); + + // Verify plan is deleted + const afterDelete = await session.rpc.plan.read(); + expect(afterDelete.exists).toBe(false); + expect(afterDelete.content).toBeNull(); + }); + + it("should create, list, and read workspace files", async () => { + const session = await client.createSession(); + + // Initially no files + const initialFiles = await session.rpc.workspace.listFiles(); + expect(initialFiles.files).toEqual([]); + + // Create a file + const fileContent = "Hello, workspace!"; + await session.rpc.workspace.createFile({ path: "test.txt", content: fileContent }); + + // List files + const afterCreate = await session.rpc.workspace.listFiles(); + expect(afterCreate.files).toContain("test.txt"); + + // Read file + const readResult = await session.rpc.workspace.readFile({ path: "test.txt" }); + expect(readResult.content).toBe(fileContent); + + // Create nested file + await session.rpc.workspace.createFile({ + path: "subdir/nested.txt", + content: "Nested content", + }); + + const afterNested = await session.rpc.workspace.listFiles(); + expect(afterNested.files).toContain("test.txt"); + expect(afterNested.files.some((f) => f.includes("nested.txt"))).toBe(true); + }); }); From f8c800532ace117b850a4edabfb77136784a3363 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 17 Feb 2026 10:38:35 +0000 Subject: [PATCH 4/6] fix: address PR review feedback for codegen - Python: remove unnecessary pass in empty dataclasses with methods - C#: properly type string|null as string? instead of object - Go: omit optional fields when nil instead of sending null Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Generated/Rpc.cs | 2 +- go/rpc/generated_rpc.go | 4 +++- python/copilot/generated/rpc.py | 6 ------ scripts/codegen/csharp.ts | 12 +++++++++++- scripts/codegen/go.ts | 12 +++++++++++- scripts/codegen/python.ts | 2 ++ 6 files changed, 28 insertions(+), 10 deletions(-) diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 03eb210b..3946822e 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -253,7 +253,7 @@ public class SessionPlanReadResult /// The content of plan.md, or null if it does not exist [JsonPropertyName("content")] - public object Content { get; set; } + public string? Content { get; set; } } internal class ReadRequest diff --git a/go/rpc/generated_rpc.go b/go/rpc/generated_rpc.go index ec27c4d8..c7d9b0c0 100644 --- a/go/rpc/generated_rpc.go +++ b/go/rpc/generated_rpc.go @@ -457,7 +457,9 @@ type FleetRpcApi struct { func (a *FleetRpcApi) Start(ctx context.Context, params *SessionFleetStartParams) (*SessionFleetStartResult, error) { req := map[string]interface{}{"sessionId": a.sessionID} if params != nil { - req["prompt"] = params.Prompt + if params.Prompt != nil { + req["prompt"] = *params.Prompt + } } raw, err := a.client.Request("session.fleet.start", req) if err != nil { diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index 5707e427..3b87bea5 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -565,8 +565,6 @@ def to_dict(self) -> dict: @dataclass class SessionPlanUpdateResult: - pass - @staticmethod def from_dict(obj: Any) -> 'SessionPlanUpdateResult': assert isinstance(obj, dict) @@ -596,8 +594,6 @@ def to_dict(self) -> dict: @dataclass class SessionPlanDeleteResult: - pass - @staticmethod def from_dict(obj: Any) -> 'SessionPlanDeleteResult': assert isinstance(obj, dict) @@ -661,8 +657,6 @@ def to_dict(self) -> dict: @dataclass class SessionWorkspaceCreateFileResult: - pass - @staticmethod def from_dict(obj: Any) -> 'SessionWorkspaceCreateFileResult': assert isinstance(obj, dict) diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index bae52c55..34fad586 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -67,7 +67,8 @@ function schemaTypeToCSharp(schema: JSONSchema7, required: boolean, knownTypes: if (schema.anyOf) { const nonNull = schema.anyOf.filter((s) => typeof s === "object" && s.type !== "null"); if (nonNull.length === 1 && typeof nonNull[0] === "object") { - return schemaTypeToCSharp(nonNull[0] as JSONSchema7, false, knownTypes) + "?"; + // Pass required=true to get the base type, then add "?" for nullable + return schemaTypeToCSharp(nonNull[0] as JSONSchema7, true, knownTypes) + "?"; } } if (schema.$ref) { @@ -76,6 +77,15 @@ function schemaTypeToCSharp(schema: JSONSchema7, required: boolean, knownTypes: } const type = schema.type; const format = schema.format; + // Handle type: ["string", "null"] patterns (nullable string) + if (Array.isArray(type)) { + const nonNullTypes = type.filter((t) => t !== "null"); + if (nonNullTypes.length === 1 && nonNullTypes[0] === "string") { + if (format === "uuid") return "Guid?"; + if (format === "date-time") return "DateTimeOffset?"; + return "string?"; + } + } if (type === "string") { if (format === "uuid") return required ? "Guid" : "Guid?"; if (format === "date-time") return required ? "DateTimeOffset" : "DateTimeOffset?"; diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index 6f0812a1..411d1c90 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -246,6 +246,7 @@ function emitMethod(lines: string[], receiver: string, name: string, method: Rpc const resultType = toPascalCase(method.rpcMethod) + "Result"; const paramProps = method.params?.properties || {}; + const requiredParams = new Set(method.params?.required || []); const nonSessionParams = Object.keys(paramProps).filter((k) => k !== "sessionId"); const hasParams = isSession ? nonSessionParams.length > 0 : Object.keys(paramProps).length > 0; const paramsType = hasParams ? toPascalCase(method.rpcMethod) + "Params" : ""; @@ -261,7 +262,16 @@ function emitMethod(lines: string[], receiver: string, name: string, method: Rpc if (hasParams) { lines.push(` if params != nil {`); for (const pName of nonSessionParams) { - lines.push(` req["${pName}"] = params.${toGoFieldName(pName)}`); + const goField = toGoFieldName(pName); + const isOptional = !requiredParams.has(pName); + if (isOptional) { + // Optional fields are pointers - only add when non-nil and dereference + lines.push(` if params.${goField} != nil {`); + lines.push(` req["${pName}"] = *params.${goField}`); + lines.push(` }`); + } else { + lines.push(` req["${pName}"] = params.${goField}`); + } } lines.push(` }`); } diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index 1080f632..aa688782 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -160,6 +160,8 @@ async function generateRpc(schemaPath?: string): Promise { typesCode = typesCode.replace(/: Any$/gm, ": Any = None"); // Fix bare except: to use Exception (required by ruff/pylint) typesCode = typesCode.replace(/except:/g, "except Exception:"); + // Remove unnecessary pass when class has methods (quicktype generates pass for empty schemas) + typesCode = typesCode.replace(/^(\s*)pass\n\n(\s*@staticmethod)/gm, "$2"); const lines: string[] = []; lines.push(`""" From dd062819f017b2543d5def664ddade5a3f01d9fe Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 17 Feb 2026 10:41:05 +0000 Subject: [PATCH 5/6] fix: add enum type safety for Mode properties in C# RPC types The mode property now uses SessionModeGetResultMode enum with Interactive, Plan, and Autopilot values for compile-time validation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Generated/Rpc.cs | 18 +++++++++++++++--- scripts/codegen/csharp.ts | 9 +++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 3946822e..4d19e2b3 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -220,7 +220,7 @@ public class SessionModeGetResult { /// The current agent mode. [JsonPropertyName("mode")] - public string Mode { get; set; } = string.Empty; + public SessionModeGetResultMode Mode { get; set; } } internal class GetRequest @@ -233,7 +233,7 @@ public class SessionModeSetResult { /// The agent mode after switching. [JsonPropertyName("mode")] - public string Mode { get; set; } = string.Empty; + public SessionModeGetResultMode Mode { get; set; } } internal class SetRequest @@ -242,7 +242,7 @@ internal class SetRequest public string SessionId { get; set; } = string.Empty; [JsonPropertyName("mode")] - public string Mode { get; set; } = string.Empty; + public SessionModeGetResultMode Mode { get; set; } } public class SessionPlanReadResult @@ -346,6 +346,18 @@ internal class StartRequest public string? Prompt { get; set; } } +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum SessionModeGetResultMode +{ + [JsonStringEnumMemberName("interactive")] + Interactive, + [JsonStringEnumMemberName("plan")] + Plan, + [JsonStringEnumMemberName("autopilot")] + Autopilot, +} + + /// Typed server-scoped RPC methods (no session required). public class ServerRpc { diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index 34fad586..dcf4a6aa 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -459,6 +459,7 @@ export async function generateSessionEvents(schemaPath?: string): Promise let emittedRpcClasses = new Set(); let rpcKnownTypes = new Map(); +let rpcEnumOutput: string[] = []; function singularPascal(s: string): string { const p = toPascalCase(s); @@ -466,6 +467,11 @@ function singularPascal(s: string): string { } function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassName: string, propName: string, classes: string[]): string { + // Handle enums (string unions like "interactive" | "plan" | "autopilot") + if (schema.enum && Array.isArray(schema.enum)) { + const enumName = getOrCreateEnum(parentClassName, propName, schema.enum as string[], rpcEnumOutput); + return isRequired ? enumName : `${enumName}?`; + } if (schema.type === "object" && schema.properties) { const className = `${parentClassName}${propName}`; classes.push(emitRpcClass(className, schema, "public", classes)); @@ -708,6 +714,8 @@ function emitSessionApiClass(className: string, node: Record, c function generateRpcCode(schema: ApiSchema): string { emittedRpcClasses.clear(); rpcKnownTypes.clear(); + rpcEnumOutput = []; + generatedEnums.clear(); // Clear shared enum deduplication map const classes: string[] = []; let serverRpcParts: string[] = []; @@ -730,6 +738,7 @@ namespace GitHub.Copilot.SDK.Rpc; `); for (const cls of classes) if (cls) lines.push(cls, ""); + for (const enumCode of rpcEnumOutput) lines.push(enumCode, ""); for (const part of serverRpcParts) lines.push(part, ""); for (const part of sessionRpcParts) lines.push(part, ""); From a60689aaa954721b2406e8f37688fbad7726a65c Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 17 Feb 2026 10:45:16 +0000 Subject: [PATCH 6/6] test: add E2E tests for new RPC features across all SDKs - Python: test_rpc.py - mode, plan, workspace tests - Go: rpc_test.go - mode, plan, workspace tests - .NET: RpcTests.cs - mode, plan, workspace tests - Fix C# codegen to use enum type for method signatures (not just request classes) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Generated/Rpc.cs | 2 +- dotnet/test/RpcTests.cs | 80 ++++++++++++++++ go/internal/e2e/rpc_test.go | 182 ++++++++++++++++++++++++++++++++++++ python/e2e/test_rpc.py | 120 ++++++++++++++++++++++++ scripts/codegen/csharp.ts | 2 +- 5 files changed, 384 insertions(+), 2 deletions(-) diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 4d19e2b3..ac010ed8 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -513,7 +513,7 @@ public async Task GetAsync(CancellationToken cancellationT } /// Calls "session.mode.set". - public async Task SetAsync(string mode, CancellationToken cancellationToken = default) + public async Task SetAsync(SessionModeGetResultMode mode, CancellationToken cancellationToken = default) { var request = new SetRequest { SessionId = _sessionId, Mode = mode }; return await CopilotClient.InvokeRpcAsync(_rpc, "session.mode.set", [request], cancellationToken); diff --git a/dotnet/test/RpcTests.cs b/dotnet/test/RpcTests.cs index 9d670295..818bc876 100644 --- a/dotnet/test/RpcTests.cs +++ b/dotnet/test/RpcTests.cs @@ -2,6 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ +using GitHub.Copilot.SDK.Rpc; using GitHub.Copilot.SDK.Test.Harness; using Xunit; using Xunit.Abstractions; @@ -79,4 +80,83 @@ public async Task Should_Call_Session_Rpc_Model_SwitchTo() var after = await session.Rpc.Model.GetCurrentAsync(); Assert.Equal("gpt-4.1", after.ModelId); } + + [Fact] + public async Task Should_Get_And_Set_Session_Mode() + { + var session = await Client.CreateSessionAsync(); + + // Get initial mode (default should be interactive) + var initial = await session.Rpc.Mode.GetAsync(); + Assert.Equal(SessionModeGetResultMode.Interactive, initial.Mode); + + // Switch to plan mode + var planResult = await session.Rpc.Mode.SetAsync(SessionModeGetResultMode.Plan); + Assert.Equal(SessionModeGetResultMode.Plan, planResult.Mode); + + // Verify mode persisted + var afterPlan = await session.Rpc.Mode.GetAsync(); + Assert.Equal(SessionModeGetResultMode.Plan, afterPlan.Mode); + + // Switch back to interactive + var interactiveResult = await session.Rpc.Mode.SetAsync(SessionModeGetResultMode.Interactive); + Assert.Equal(SessionModeGetResultMode.Interactive, interactiveResult.Mode); + } + + [Fact] + public async Task Should_Read_Update_And_Delete_Plan() + { + var session = await Client.CreateSessionAsync(); + + // Initially plan should not exist + var initial = await session.Rpc.Plan.ReadAsync(); + Assert.False(initial.Exists); + Assert.Null(initial.Content); + + // Create/update plan + var planContent = "# Test Plan\n\n- Step 1\n- Step 2"; + await session.Rpc.Plan.UpdateAsync(planContent); + + // Verify plan exists and has correct content + var afterUpdate = await session.Rpc.Plan.ReadAsync(); + Assert.True(afterUpdate.Exists); + Assert.Equal(planContent, afterUpdate.Content); + + // Delete plan + await session.Rpc.Plan.DeleteAsync(); + + // Verify plan is deleted + var afterDelete = await session.Rpc.Plan.ReadAsync(); + Assert.False(afterDelete.Exists); + Assert.Null(afterDelete.Content); + } + + [Fact] + public async Task Should_Create_List_And_Read_Workspace_Files() + { + var session = await Client.CreateSessionAsync(); + + // Initially no files + var initialFiles = await session.Rpc.Workspace.ListFilesAsync(); + Assert.Empty(initialFiles.Files); + + // Create a file + var fileContent = "Hello, workspace!"; + await session.Rpc.Workspace.CreateFileAsync("test.txt", fileContent); + + // List files + var afterCreate = await session.Rpc.Workspace.ListFilesAsync(); + Assert.Contains("test.txt", afterCreate.Files); + + // Read file + var readResult = await session.Rpc.Workspace.ReadFileAsync("test.txt"); + Assert.Equal(fileContent, readResult.Content); + + // Create nested file + await session.Rpc.Workspace.CreateFileAsync("subdir/nested.txt", "Nested content"); + + var afterNested = await session.Rpc.Workspace.ListFilesAsync(); + Assert.Contains("test.txt", afterNested.Files); + Assert.Contains(afterNested.Files, f => f.Contains("nested.txt")); + } } diff --git a/go/internal/e2e/rpc_test.go b/go/internal/e2e/rpc_test.go index 07916646..43b7cafa 100644 --- a/go/internal/e2e/rpc_test.go +++ b/go/internal/e2e/rpc_test.go @@ -1,6 +1,7 @@ package e2e import ( + "strings" "testing" copilot "github.com/github/copilot-sdk/go" @@ -185,4 +186,185 @@ func TestSessionRpc(t *testing.T) { t.Errorf("Expected modelId 'gpt-4.1' after switch, got %v", after.ModelID) } }) + + t.Run("should get and set session mode", func(t *testing.T) { + session, err := client.CreateSession(t.Context(), nil) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + + // Get initial mode (default should be interactive) + initial, err := session.RPC.Mode.Get(t.Context()) + if err != nil { + t.Fatalf("Failed to get mode: %v", err) + } + if initial.Mode != rpc.Interactive { + t.Errorf("Expected initial mode 'interactive', got %q", initial.Mode) + } + + // Switch to plan mode + planResult, err := session.RPC.Mode.Set(t.Context(), &rpc.SessionModeSetParams{Mode: rpc.Plan}) + if err != nil { + t.Fatalf("Failed to set mode to plan: %v", err) + } + if planResult.Mode != rpc.Plan { + t.Errorf("Expected mode 'plan', got %q", planResult.Mode) + } + + // Verify mode persisted + afterPlan, err := session.RPC.Mode.Get(t.Context()) + if err != nil { + t.Fatalf("Failed to get mode after plan: %v", err) + } + if afterPlan.Mode != rpc.Plan { + t.Errorf("Expected mode 'plan' after set, got %q", afterPlan.Mode) + } + + // Switch back to interactive + interactiveResult, err := session.RPC.Mode.Set(t.Context(), &rpc.SessionModeSetParams{Mode: rpc.Interactive}) + if err != nil { + t.Fatalf("Failed to set mode to interactive: %v", err) + } + if interactiveResult.Mode != rpc.Interactive { + t.Errorf("Expected mode 'interactive', got %q", interactiveResult.Mode) + } + }) + + t.Run("should read, update, and delete plan", func(t *testing.T) { + session, err := client.CreateSession(t.Context(), nil) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + + // Initially plan should not exist + initial, err := session.RPC.Plan.Read(t.Context()) + if err != nil { + t.Fatalf("Failed to read plan: %v", err) + } + if initial.Exists { + t.Error("Expected plan to not exist initially") + } + if initial.Content != nil { + t.Error("Expected content to be nil initially") + } + + // Create/update plan + planContent := "# Test Plan\n\n- Step 1\n- Step 2" + _, err = session.RPC.Plan.Update(t.Context(), &rpc.SessionPlanUpdateParams{Content: planContent}) + if err != nil { + t.Fatalf("Failed to update plan: %v", err) + } + + // Verify plan exists and has correct content + afterUpdate, err := session.RPC.Plan.Read(t.Context()) + if err != nil { + t.Fatalf("Failed to read plan after update: %v", err) + } + if !afterUpdate.Exists { + t.Error("Expected plan to exist after update") + } + if afterUpdate.Content == nil || *afterUpdate.Content != planContent { + t.Errorf("Expected content %q, got %v", planContent, afterUpdate.Content) + } + + // Delete plan + _, err = session.RPC.Plan.Delete(t.Context()) + if err != nil { + t.Fatalf("Failed to delete plan: %v", err) + } + + // Verify plan is deleted + afterDelete, err := session.RPC.Plan.Read(t.Context()) + if err != nil { + t.Fatalf("Failed to read plan after delete: %v", err) + } + if afterDelete.Exists { + t.Error("Expected plan to not exist after delete") + } + if afterDelete.Content != nil { + t.Error("Expected content to be nil after delete") + } + }) + + t.Run("should create, list, and read workspace files", func(t *testing.T) { + session, err := client.CreateSession(t.Context(), nil) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + + // Initially no files + initialFiles, err := session.RPC.Workspace.ListFiles(t.Context()) + if err != nil { + t.Fatalf("Failed to list files: %v", err) + } + if len(initialFiles.Files) != 0 { + t.Errorf("Expected no files initially, got %v", initialFiles.Files) + } + + // Create a file + fileContent := "Hello, workspace!" + _, err = session.RPC.Workspace.CreateFile(t.Context(), &rpc.SessionWorkspaceCreateFileParams{ + Path: "test.txt", + Content: fileContent, + }) + if err != nil { + t.Fatalf("Failed to create file: %v", err) + } + + // List files + afterCreate, err := session.RPC.Workspace.ListFiles(t.Context()) + if err != nil { + t.Fatalf("Failed to list files after create: %v", err) + } + if !containsString(afterCreate.Files, "test.txt") { + t.Errorf("Expected files to contain 'test.txt', got %v", afterCreate.Files) + } + + // Read file + readResult, err := session.RPC.Workspace.ReadFile(t.Context(), &rpc.SessionWorkspaceReadFileParams{ + Path: "test.txt", + }) + if err != nil { + t.Fatalf("Failed to read file: %v", err) + } + if readResult.Content != fileContent { + t.Errorf("Expected content %q, got %q", fileContent, readResult.Content) + } + + // Create nested file + _, err = session.RPC.Workspace.CreateFile(t.Context(), &rpc.SessionWorkspaceCreateFileParams{ + Path: "subdir/nested.txt", + Content: "Nested content", + }) + if err != nil { + t.Fatalf("Failed to create nested file: %v", err) + } + + afterNested, err := session.RPC.Workspace.ListFiles(t.Context()) + if err != nil { + t.Fatalf("Failed to list files after nested: %v", err) + } + if !containsString(afterNested.Files, "test.txt") { + t.Errorf("Expected files to contain 'test.txt', got %v", afterNested.Files) + } + hasNested := false + for _, f := range afterNested.Files { + if strings.Contains(f, "nested.txt") { + hasNested = true + break + } + } + if !hasNested { + t.Errorf("Expected files to contain 'nested.txt', got %v", afterNested.Files) + } + }) +} + +func containsString(slice []string, str string) bool { + for _, s := range slice { + if s == str { + return true + } + } + return false } diff --git a/python/e2e/test_rpc.py b/python/e2e/test_rpc.py index bc598a69..da2ba3eb 100644 --- a/python/e2e/test_rpc.py +++ b/python/e2e/test_rpc.py @@ -102,3 +102,123 @@ async def test_should_call_session_rpc_model_switch_to(self, ctx: E2ETestContext # Verify the switch persisted after = await session.rpc.model.get_current() assert after.model_id == "gpt-4.1" + + @pytest.mark.asyncio + async def test_get_and_set_session_mode(self): + """Test getting and setting session mode""" + from copilot.generated.rpc import Mode, SessionModeSetParams + + client = CopilotClient({"cli_path": CLI_PATH, "use_stdio": True}) + + try: + await client.start() + session = await client.create_session({}) + + # Get initial mode (default should be interactive) + initial = await session.rpc.mode.get() + assert initial.mode == Mode.INTERACTIVE + + # Switch to plan mode + plan_result = await session.rpc.mode.set(SessionModeSetParams(mode=Mode.PLAN)) + assert plan_result.mode == Mode.PLAN + + # Verify mode persisted + after_plan = await session.rpc.mode.get() + assert after_plan.mode == Mode.PLAN + + # Switch back to interactive + interactive_result = await session.rpc.mode.set( + SessionModeSetParams(mode=Mode.INTERACTIVE) + ) + assert interactive_result.mode == Mode.INTERACTIVE + + await session.destroy() + await client.stop() + finally: + await client.force_stop() + + @pytest.mark.asyncio + async def test_read_update_and_delete_plan(self): + """Test reading, updating, and deleting plan""" + from copilot.generated.rpc import SessionPlanUpdateParams + + client = CopilotClient({"cli_path": CLI_PATH, "use_stdio": True}) + + try: + await client.start() + session = await client.create_session({}) + + # Initially plan should not exist + initial = await session.rpc.plan.read() + assert initial.exists is False + assert initial.content is None + + # Create/update plan + plan_content = "# Test Plan\n\n- Step 1\n- Step 2" + await session.rpc.plan.update(SessionPlanUpdateParams(content=plan_content)) + + # Verify plan exists and has correct content + after_update = await session.rpc.plan.read() + assert after_update.exists is True + assert after_update.content == plan_content + + # Delete plan + await session.rpc.plan.delete() + + # Verify plan is deleted + after_delete = await session.rpc.plan.read() + assert after_delete.exists is False + assert after_delete.content is None + + await session.destroy() + await client.stop() + finally: + await client.force_stop() + + @pytest.mark.asyncio + async def test_create_list_and_read_workspace_files(self): + """Test creating, listing, and reading workspace files""" + from copilot.generated.rpc import ( + SessionWorkspaceCreateFileParams, + SessionWorkspaceReadFileParams, + ) + + client = CopilotClient({"cli_path": CLI_PATH, "use_stdio": True}) + + try: + await client.start() + session = await client.create_session({}) + + # Initially no files + initial_files = await session.rpc.workspace.list_files() + assert initial_files.files == [] + + # Create a file + file_content = "Hello, workspace!" + await session.rpc.workspace.create_file( + SessionWorkspaceCreateFileParams(content=file_content, path="test.txt") + ) + + # List files + after_create = await session.rpc.workspace.list_files() + assert "test.txt" in after_create.files + + # Read file + read_result = await session.rpc.workspace.read_file( + SessionWorkspaceReadFileParams(path="test.txt") + ) + assert read_result.content == file_content + + # Create nested file + await session.rpc.workspace.create_file( + SessionWorkspaceCreateFileParams(content="Nested content", path="subdir/nested.txt") + ) + + after_nested = await session.rpc.workspace.list_files() + assert "test.txt" in after_nested.files + assert any("nested.txt" in f for f in after_nested.files) + + await session.destroy() + await client.stop() + finally: + await client.force_stop() diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index dcf4a6aa..e5e0fcf9 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -697,7 +697,7 @@ function emitSessionApiClass(className: string, node: Record, c for (const [pName, pSchema] of paramEntries) { if (typeof pSchema !== "object") continue; - const csType = schemaTypeToCSharp(pSchema as JSONSchema7, requiredSet.has(pName), rpcKnownTypes); + const csType = resolveRpcType(pSchema as JSONSchema7, requiredSet.has(pName), requestClassName, toPascalCase(pName), classes); sigParams.push(`${csType} ${pName}`); bodyAssignments.push(`${toPascalCase(pName)} = ${pName}`); }