diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs
index c88ae790..ac010ed8 100644
--- a/dotnet/src/Generated/Rpc.cs
+++ b/dotnet/src/Generated/Rpc.cs
@@ -216,6 +216,148 @@ internal class SwitchToRequest
public string ModelId { get; set; } = string.Empty;
}
+public class SessionModeGetResult
+{
+ /// The current agent mode.
+ [JsonPropertyName("mode")]
+ public SessionModeGetResultMode Mode { get; set; }
+}
+
+internal class GetRequest
+{
+ [JsonPropertyName("sessionId")]
+ public string SessionId { get; set; } = string.Empty;
+}
+
+public class SessionModeSetResult
+{
+ /// The agent mode after switching.
+ [JsonPropertyName("mode")]
+ public SessionModeGetResultMode Mode { get; set; }
+}
+
+internal class SetRequest
+{
+ [JsonPropertyName("sessionId")]
+ public string SessionId { get; set; } = string.Empty;
+
+ [JsonPropertyName("mode")]
+ public SessionModeGetResultMode Mode { get; set; }
+}
+
+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 string? 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; }
+}
+
+[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
{
@@ -309,9 +451,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 +494,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(SessionModeGetResultMode 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 +626,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/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/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/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/go/rpc/generated_rpc.go b/go/rpc/generated_rpc.go
index bca3859e..c7d9b0c0 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,178 @@ 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 {
+ if params.Prompt != 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/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"
},
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/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);
+ });
});
diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py
index 14a1ec7c..3b87bea5 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,252 @@ 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:
+ @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:
+ @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:
+ @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 +796,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 +960,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"
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 bae52c55..e5e0fcf9 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?";
@@ -449,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);
@@ -456,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));
@@ -681,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}`);
}
@@ -698,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[] = [];
@@ -720,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, "");
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(`"""