diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 34598b6a2..9608d98c3 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -637,6 +637,13 @@ private void ApplyConfigDefaultsForMode(SessionConfigBase config) if (_options.Mode == CopilotClientMode.Empty) { config.EnableSessionTelemetry ??= false; + config.SkipEmbeddingRetrieval ??= true; + config.EmbeddingCacheStorage ??= EmbeddingCacheStorageMode.InMemory; + config.EnableOnDemandInstructionDiscovery ??= false; + config.EnableFileHooks ??= false; + config.EnableHostGitOperations ??= false; + config.EnableSessionStore ??= false; + config.EnableSkills ??= false; config.McpOAuthTokenStorage ??= McpOAuthTokenStorageMode.InMemory; } } @@ -876,6 +883,14 @@ public async Task CreateSessionAsync(SessionConfig config, Cance config.Agent, config.ConfigDirectory, config.EnableConfigDiscovery, + config.SkipEmbeddingRetrieval, + config.EmbeddingCacheStorage, + config.OrganizationCustomInstructions, + config.EnableOnDemandInstructionDiscovery, + config.EnableFileHooks, + config.EnableHostGitOperations, + config.EnableSessionStore, + config.EnableSkills, config.SkillDirectories, config.DisabledSkills, config.InfiniteSessions, @@ -1053,6 +1068,14 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes config.WorkingDirectory, config.ConfigDirectory, config.EnableConfigDiscovery, + config.SkipEmbeddingRetrieval, + config.EmbeddingCacheStorage, + config.OrganizationCustomInstructions, + config.EnableOnDemandInstructionDiscovery, + config.EnableFileHooks, + config.EnableHostGitOperations, + config.EnableSessionStore, + config.EnableSkills, config.SuppressResumeEvent is true ? true : null, config.Streaming is true ? true : null, config.IncludeSubAgentStreamingEvents, @@ -2179,6 +2202,14 @@ internal record CreateSessionRequest( string? Agent, [property: JsonPropertyName("configDir")] string? ConfigDirectory, bool? EnableConfigDiscovery, + bool? SkipEmbeddingRetrieval, + EmbeddingCacheStorageMode? EmbeddingCacheStorage, + string? OrganizationCustomInstructions, + bool? EnableOnDemandInstructionDiscovery, + bool? EnableFileHooks, + bool? EnableHostGitOperations, + bool? EnableSessionStore, + bool? EnableSkills, IList? SkillDirectories, IList? DisabledSkills, InfiniteSessionConfig? InfiniteSessions, @@ -2247,6 +2278,14 @@ internal record ResumeSessionRequest( string? WorkingDirectory, [property: JsonPropertyName("configDir")] string? ConfigDirectory, bool? EnableConfigDiscovery, + bool? SkipEmbeddingRetrieval, + EmbeddingCacheStorageMode? EmbeddingCacheStorage, + string? OrganizationCustomInstructions, + bool? EnableOnDemandInstructionDiscovery, + bool? EnableFileHooks, + bool? EnableHostGitOperations, + bool? EnableSessionStore, + bool? EnableSkills, bool? SuppressResumeEvent, bool? Streaming, bool? IncludeSubAgentStreamingEvents, @@ -2349,6 +2388,7 @@ internal record HooksInvokeResponse( [JsonSerializable(typeof(GetSessionMetadataRequest))] [JsonSerializable(typeof(GetSessionMetadataResponse))] [JsonSerializable(typeof(McpOAuthTokenStorageMode))] + [JsonSerializable(typeof(EmbeddingCacheStorageMode))] [JsonSerializable(typeof(ModelCapabilitiesOverride))] [JsonSerializable(typeof(ProviderConfig))] [JsonSerializable(typeof(ResumeSessionRequest))] diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index 05bab6e87..2e1a217b3 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -2096,6 +2096,21 @@ public enum McpOAuthTokenStorageMode InMemory } +/// +/// Controls how the embedding cache is stored for a session. +/// +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum EmbeddingCacheStorageMode +{ + /// Embeddings are cached on disk, shared across sessions and restarts. + [JsonStringEnumMemberName("persistent")] + Persistent, + + /// Embeddings are cached in memory only and discarded when the session ends. + [JsonStringEnumMemberName("in-memory")] + InMemory +} + /// /// Abstract base class for MCP server configurations. /// @@ -2402,6 +2417,14 @@ protected SessionConfigBase(SessionConfigBase? other) Agent = other.Agent; DisabledSkills = other.DisabledSkills is not null ? [.. other.DisabledSkills] : null; EnableConfigDiscovery = other.EnableConfigDiscovery; + SkipEmbeddingRetrieval = other.SkipEmbeddingRetrieval; + EmbeddingCacheStorage = other.EmbeddingCacheStorage; + OrganizationCustomInstructions = other.OrganizationCustomInstructions; + EnableOnDemandInstructionDiscovery = other.EnableOnDemandInstructionDiscovery; + EnableFileHooks = other.EnableFileHooks; + EnableHostGitOperations = other.EnableHostGitOperations; + EnableSessionStore = other.EnableSessionStore; + EnableSkills = other.EnableSkills; EnableMcpApps = other.EnableMcpApps; ExcludedTools = other.ExcludedTools is not null ? [.. other.ExcludedTools] : null; Hooks = other.Hooks; @@ -2492,6 +2515,63 @@ protected SessionConfigBase(SessionConfigBase? other) /// public bool? EnableConfigDiscovery { get; set; } + /// + /// When , skips embedding-based retrieval for this session. + /// Use in multitenant deployments to prevent cross-session information leakage + /// through the shared embedding cache. + /// + public bool? SkipEmbeddingRetrieval { get; set; } + + /// + /// Controls how the embedding cache is stored for this session. + /// : Embeddings are cached on disk and shared across sessions/restarts. + /// : Embeddings are cached in memory only and discarded when the session ends. + /// + public EmbeddingCacheStorageMode? EmbeddingCacheStorage { get; set; } + + /// + /// Organization-level custom instructions to include in the system prompt. + /// Allows hosts to inject organization-specific guidance without relying on + /// filesystem-based instruction discovery. + /// + public string? OrganizationCustomInstructions { get; set; } + + /// + /// When , enables on-demand discovery of instruction files + /// (for example AGENTS.md and .github/copilot-instructions.md) + /// after successful file views. + /// + public bool? EnableOnDemandInstructionDiscovery { get; set; } + + /// + /// When , enables loading of file-based hooks from + /// .github/hooks/. This is separate from , which + /// controls SDK hook callback registration. + /// + public bool? EnableFileHooks { get; set; } + + /// + /// When , enables git operations on the host filesystem + /// such as branch detection, file status, and commit history. When + /// , no git context is surfaced in the system prompt. + /// + public bool? EnableHostGitOperations { get; set; } + + /// + /// When , enables the cross-session store for search and + /// retrieval across sessions. When , session content is + /// not written to or read from the shared session store. + /// + public bool? EnableSessionStore { get; set; } + + /// + /// When , enables skill loading, including built-in + /// skills and discovered skill directories. When , no + /// skills are loaded regardless of or + /// . + /// + public bool? EnableSkills { get; set; } + /// /// Custom tool declarations available to the language model during the session. /// Declarations backed by an are invoked automatically; declarations without one diff --git a/dotnet/test/E2E/ClientOptionsE2ETests.cs b/dotnet/test/E2E/ClientOptionsE2ETests.cs index 0257c57db..6360cb55a 100644 --- a/dotnet/test/E2E/ClientOptionsE2ETests.cs +++ b/dotnet/test/E2E/ClientOptionsE2ETests.cs @@ -177,6 +177,82 @@ public async Task Should_Omit_EnableSessionTelemetry_When_Not_Set() await session.DisposeAsync(); } + [Fact] + public async Task Should_Forward_Granular_Multitenancy_Fields_In_Create_Wire_Request() + { + var (cliPath, capturePath) = await CreateFakeCliCaptureAsync(); + + await using var client = Ctx.CreateClient(options: new CopilotClientOptions + { + Connection = RuntimeConnection.ForStdio(path: cliPath, args: ["--capture-file", capturePath]), + UseLoggedInUser = false, + }); + + await client.StartAsync(); + + var session = await client.CreateSessionAsync(new SessionConfig + { + SkipEmbeddingRetrieval = false, + OrganizationCustomInstructions = "Follow org policy.", + EnableOnDemandInstructionDiscovery = true, + EmbeddingCacheStorage = EmbeddingCacheStorageMode.Persistent, + EnableFileHooks = true, + EnableHostGitOperations = false, + EnableSessionStore = true, + EnableSkills = false, + OnPermissionRequest = PermissionHandler.ApproveAll, + }); + + using var capture = JsonDocument.Parse(await File.ReadAllTextAsync(capturePath)); + var createRequest = GetCapturedRequestParams(capture.RootElement, "session.create"); + Assert.False(createRequest.GetProperty("skipEmbeddingRetrieval").GetBoolean()); + Assert.Equal("Follow org policy.", createRequest.GetProperty("organizationCustomInstructions").GetString()); + Assert.True(createRequest.GetProperty("enableOnDemandInstructionDiscovery").GetBoolean()); + Assert.Equal("persistent", createRequest.GetProperty("embeddingCacheStorage").GetString()); + Assert.True(createRequest.GetProperty("enableFileHooks").GetBoolean()); + Assert.False(createRequest.GetProperty("enableHostGitOperations").GetBoolean()); + Assert.True(createRequest.GetProperty("enableSessionStore").GetBoolean()); + Assert.False(createRequest.GetProperty("enableSkills").GetBoolean()); + + await session.DisposeAsync(); + } + + [Fact] + public async Task Should_Apply_Empty_Mode_Defaults_To_CreateSession_Wire_Request() + { + var (cliPath, capturePath) = await CreateFakeCliCaptureAsync(); + + await using var client = Ctx.CreateClient(options: new CopilotClientOptions + { + Connection = RuntimeConnection.ForStdio(path: cliPath, args: ["--capture-file", capturePath]), + Mode = CopilotClientMode.Empty, + BaseDirectory = Ctx.WorkDir, + UseLoggedInUser = false, + }); + + await client.StartAsync(); + + var session = await client.CreateSessionAsync(new SessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll, + AvailableTools = new ToolSet().AddBuiltIn(BuiltInTools.Isolated), + }); + + using var capture = JsonDocument.Parse(await File.ReadAllTextAsync(capturePath)); + var createRequest = GetCapturedRequestParams(capture.RootElement, "session.create"); + Assert.False(createRequest.GetProperty("enableSessionTelemetry").GetBoolean()); + Assert.True(createRequest.GetProperty("skipEmbeddingRetrieval").GetBoolean()); + Assert.False(createRequest.GetProperty("enableOnDemandInstructionDiscovery").GetBoolean()); + Assert.Equal("in-memory", createRequest.GetProperty("embeddingCacheStorage").GetString()); + Assert.False(createRequest.GetProperty("enableFileHooks").GetBoolean()); + Assert.False(createRequest.GetProperty("enableHostGitOperations").GetBoolean()); + Assert.False(createRequest.GetProperty("enableSessionStore").GetBoolean()); + Assert.False(createRequest.GetProperty("enableSkills").GetBoolean()); + Assert.False(createRequest.TryGetProperty("organizationCustomInstructions", out _)); + + await session.DisposeAsync(); + } + [Fact] public async Task Should_Propagate_Activity_TraceContext_To_Session_Create_And_Send() { @@ -293,6 +369,82 @@ public async Task Should_Propagate_Activity_TraceContext_To_Session_Resume() await session.DisposeAsync(); } + [Fact] + public async Task Should_Forward_Granular_Multitenancy_Fields_In_Resume_Wire_Request() + { + var (cliPath, capturePath) = await CreateFakeCliCaptureAsync(); + + await using var client = Ctx.CreateClient(options: new CopilotClientOptions + { + Connection = RuntimeConnection.ForStdio(path: cliPath, args: ["--capture-file", capturePath]), + UseLoggedInUser = false, + }); + + await client.StartAsync(); + + var session = await client.ResumeSessionAsync("resume-session", new ResumeSessionConfig + { + SkipEmbeddingRetrieval = false, + OrganizationCustomInstructions = "Resume org policy.", + EnableOnDemandInstructionDiscovery = true, + EmbeddingCacheStorage = EmbeddingCacheStorageMode.Persistent, + EnableFileHooks = true, + EnableHostGitOperations = false, + EnableSessionStore = true, + EnableSkills = false, + OnPermissionRequest = PermissionHandler.ApproveAll, + }); + + using var capture = JsonDocument.Parse(await File.ReadAllTextAsync(capturePath)); + var resumeRequest = GetCapturedRequestParams(capture.RootElement, "session.resume"); + Assert.False(resumeRequest.GetProperty("skipEmbeddingRetrieval").GetBoolean()); + Assert.Equal("Resume org policy.", resumeRequest.GetProperty("organizationCustomInstructions").GetString()); + Assert.True(resumeRequest.GetProperty("enableOnDemandInstructionDiscovery").GetBoolean()); + Assert.Equal("persistent", resumeRequest.GetProperty("embeddingCacheStorage").GetString()); + Assert.True(resumeRequest.GetProperty("enableFileHooks").GetBoolean()); + Assert.False(resumeRequest.GetProperty("enableHostGitOperations").GetBoolean()); + Assert.True(resumeRequest.GetProperty("enableSessionStore").GetBoolean()); + Assert.False(resumeRequest.GetProperty("enableSkills").GetBoolean()); + + await session.DisposeAsync(); + } + + [Fact] + public async Task Should_Apply_Empty_Mode_Defaults_To_ResumeSession_Wire_Request() + { + var (cliPath, capturePath) = await CreateFakeCliCaptureAsync(); + + await using var client = Ctx.CreateClient(options: new CopilotClientOptions + { + Connection = RuntimeConnection.ForStdio(path: cliPath, args: ["--capture-file", capturePath]), + Mode = CopilotClientMode.Empty, + BaseDirectory = Ctx.WorkDir, + UseLoggedInUser = false, + }); + + await client.StartAsync(); + + var session = await client.ResumeSessionAsync("resume-empty-session", new ResumeSessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll, + AvailableTools = new ToolSet().AddBuiltIn(BuiltInTools.Isolated), + }); + + using var capture = JsonDocument.Parse(await File.ReadAllTextAsync(capturePath)); + var resumeRequest = GetCapturedRequestParams(capture.RootElement, "session.resume"); + Assert.False(resumeRequest.GetProperty("enableSessionTelemetry").GetBoolean()); + Assert.True(resumeRequest.GetProperty("skipEmbeddingRetrieval").GetBoolean()); + Assert.False(resumeRequest.GetProperty("enableOnDemandInstructionDiscovery").GetBoolean()); + Assert.Equal("in-memory", resumeRequest.GetProperty("embeddingCacheStorage").GetString()); + Assert.False(resumeRequest.GetProperty("enableFileHooks").GetBoolean()); + Assert.False(resumeRequest.GetProperty("enableHostGitOperations").GetBoolean()); + Assert.False(resumeRequest.GetProperty("enableSessionStore").GetBoolean()); + Assert.False(resumeRequest.GetProperty("enableSkills").GetBoolean()); + Assert.False(resumeRequest.TryGetProperty("organizationCustomInstructions", out _)); + + await session.DisposeAsync(); + } + [Fact] public void Should_Accept_GitHubToken_Option() { diff --git a/go/client.go b/go/client.go index b5933ce86..11c7e6234 100644 --- a/go/client.go +++ b/go/client.go @@ -609,6 +609,14 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses if config.EnableConfigDiscovery { req.EnableConfigDiscovery = Bool(true) } + req.SkipEmbeddingRetrieval = config.SkipEmbeddingRetrieval + req.EmbeddingCacheStorage = config.EmbeddingCacheStorage + req.OrganizationCustomInstructions = config.OrganizationCustomInstructions + req.EnableOnDemandInstructionDiscovery = config.EnableOnDemandInstructionDiscovery + req.EnableFileHooks = config.EnableFileHooks + req.EnableHostGitOperations = config.EnableHostGitOperations + req.EnableSessionStore = config.EnableSessionStore + req.EnableSkills = config.EnableSkills req.Tools = config.Tools systemMessage := c.systemMessageForMode(config.SystemMessage) wireSystemMessage, transformCallbacks := extractTransformCallbacks(systemMessage) @@ -952,6 +960,14 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, if config.EnableConfigDiscovery { req.EnableConfigDiscovery = Bool(true) } + req.SkipEmbeddingRetrieval = config.SkipEmbeddingRetrieval + req.EmbeddingCacheStorage = config.EmbeddingCacheStorage + req.OrganizationCustomInstructions = config.OrganizationCustomInstructions + req.EnableOnDemandInstructionDiscovery = config.EnableOnDemandInstructionDiscovery + req.EnableFileHooks = config.EnableFileHooks + req.EnableHostGitOperations = config.EnableHostGitOperations + req.EnableSessionStore = config.EnableSessionStore + req.EnableSkills = config.EnableSkills if config.SuppressResumeEvent { req.DisableResume = Bool(true) } diff --git a/go/mode_empty.go b/go/mode_empty.go index 0ab02b388..a3e58ad9c 100644 --- a/go/mode_empty.go +++ b/go/mode_empty.go @@ -126,6 +126,34 @@ func (c *Client) applyConfigDefaultsForMode(config *SessionConfig) { f := false config.EnableSessionTelemetry = &f } + if config.SkipEmbeddingRetrieval == nil { + t := true + config.SkipEmbeddingRetrieval = &t + } + if config.EmbeddingCacheStorage == nil { + inMemory := "in-memory" + config.EmbeddingCacheStorage = &inMemory + } + if config.EnableOnDemandInstructionDiscovery == nil { + f := false + config.EnableOnDemandInstructionDiscovery = &f + } + if config.EnableFileHooks == nil { + f := false + config.EnableFileHooks = &f + } + if config.EnableHostGitOperations == nil { + f := false + config.EnableHostGitOperations = &f + } + if config.EnableSessionStore == nil { + f := false + config.EnableSessionStore = &f + } + if config.EnableSkills == nil { + f := false + config.EnableSkills = &f + } if config.MCPOAuthTokenStorage == "" { config.MCPOAuthTokenStorage = "in-memory" } @@ -139,6 +167,34 @@ func (c *Client) applyResumeDefaultsForMode(config *ResumeSessionConfig) { f := false config.EnableSessionTelemetry = &f } + if config.SkipEmbeddingRetrieval == nil { + t := true + config.SkipEmbeddingRetrieval = &t + } + if config.EmbeddingCacheStorage == nil { + inMemory := "in-memory" + config.EmbeddingCacheStorage = &inMemory + } + if config.EnableOnDemandInstructionDiscovery == nil { + f := false + config.EnableOnDemandInstructionDiscovery = &f + } + if config.EnableFileHooks == nil { + f := false + config.EnableFileHooks = &f + } + if config.EnableHostGitOperations == nil { + f := false + config.EnableHostGitOperations = &f + } + if config.EnableSessionStore == nil { + f := false + config.EnableSessionStore = &f + } + if config.EnableSkills == nil { + f := false + config.EnableSkills = &f + } if config.MCPOAuthTokenStorage == "" { config.MCPOAuthTokenStorage = "in-memory" } diff --git a/go/toolset_test.go b/go/toolset_test.go index 80b49f619..babe63502 100644 --- a/go/toolset_test.go +++ b/go/toolset_test.go @@ -248,6 +248,94 @@ func TestApplyConfigDefaultsForMode_copilotCliLeavesNil(t *testing.T) { } } +func TestApplyConfigDefaultsForMode_emptyDefaultsGranularFlags(t *testing.T) { + c := NewClient(&ClientOptions{Mode: ModeEmpty, BaseDirectory: t.TempDir()}) + cfg := &SessionConfig{} + c.applyConfigDefaultsForMode(cfg) + if cfg.SkipEmbeddingRetrieval == nil || *cfg.SkipEmbeddingRetrieval != true { + t.Errorf("expected SkipEmbeddingRetrieval=true in empty mode, got %v", cfg.SkipEmbeddingRetrieval) + } + if cfg.EmbeddingCacheStorage == nil || *cfg.EmbeddingCacheStorage != *String("in-memory") { + t.Errorf("expected EmbeddingCacheStorage=in-memory in empty mode, got %v", cfg.EmbeddingCacheStorage) + } + if cfg.EnableOnDemandInstructionDiscovery == nil || *cfg.EnableOnDemandInstructionDiscovery != false { + t.Errorf("expected EnableOnDemandInstructionDiscovery=false in empty mode, got %v", cfg.EnableOnDemandInstructionDiscovery) + } + if cfg.EnableFileHooks == nil || *cfg.EnableFileHooks != false { + t.Errorf("expected EnableFileHooks=false in empty mode, got %v", cfg.EnableFileHooks) + } + if cfg.EnableHostGitOperations == nil || *cfg.EnableHostGitOperations != false { + t.Errorf("expected EnableHostGitOperations=false in empty mode, got %v", cfg.EnableHostGitOperations) + } + if cfg.EnableSessionStore == nil || *cfg.EnableSessionStore != false { + t.Errorf("expected EnableSessionStore=false in empty mode, got %v", cfg.EnableSessionStore) + } + if cfg.EnableSkills == nil || *cfg.EnableSkills != false { + t.Errorf("expected EnableSkills=false in empty mode, got %v", cfg.EnableSkills) + } +} + +func TestApplyConfigDefaultsForMode_emptyHonorsCallerGranularFlags(t *testing.T) { + c := NewClient(&ClientOptions{Mode: ModeEmpty, BaseDirectory: t.TempDir()}) + falseVal := false + trueVal := true + cfg := &SessionConfig{ + SkipEmbeddingRetrieval: &falseVal, + EmbeddingCacheStorage: String("persistent"), + EnableOnDemandInstructionDiscovery: &trueVal, + EnableFileHooks: &trueVal, + EnableHostGitOperations: &trueVal, + EnableSessionStore: &trueVal, + EnableSkills: &trueVal, + } + c.applyConfigDefaultsForMode(cfg) + if *cfg.SkipEmbeddingRetrieval != false { + t.Errorf("caller-supplied SkipEmbeddingRetrieval must win") + } + if *cfg.EmbeddingCacheStorage != *String("persistent") { + t.Errorf("caller-supplied EmbeddingCacheStorage must win") + } + if *cfg.EnableOnDemandInstructionDiscovery != true { + t.Errorf("caller-supplied EnableOnDemandInstructionDiscovery must win") + } + if *cfg.EnableFileHooks != true { + t.Errorf("caller-supplied EnableFileHooks must win") + } + if *cfg.EnableHostGitOperations != true { + t.Errorf("caller-supplied EnableHostGitOperations must win") + } + if *cfg.EnableSessionStore != true { + t.Errorf("caller-supplied EnableSessionStore must win") + } + if *cfg.EnableSkills != true { + t.Errorf("caller-supplied EnableSkills must win") + } +} + +func TestApplyConfigDefaultsForMode_copilotCliLeavesGranularFlagsNil(t *testing.T) { + c := NewClient(&ClientOptions{Mode: ModeCopilotCli}) + cfg := &SessionConfig{} + c.applyConfigDefaultsForMode(cfg) + if cfg.SkipEmbeddingRetrieval != nil { + t.Errorf("non-empty mode must not default SkipEmbeddingRetrieval") + } + if cfg.EnableOnDemandInstructionDiscovery != nil { + t.Errorf("non-empty mode must not default EnableOnDemandInstructionDiscovery") + } + if cfg.EnableFileHooks != nil { + t.Errorf("non-empty mode must not default EnableFileHooks") + } + if cfg.EnableHostGitOperations != nil { + t.Errorf("non-empty mode must not default EnableHostGitOperations") + } + if cfg.EnableSessionStore != nil { + t.Errorf("non-empty mode must not default EnableSessionStore") + } + if cfg.EnableSkills != nil { + t.Errorf("non-empty mode must not default EnableSkills") + } +} + func TestApplyConfigDefaultsForMode_emptyDefaultsMCPOAuthTokenStorage(t *testing.T) { c := NewClient(&ClientOptions{Mode: ModeEmpty, BaseDirectory: t.TempDir()}) cfg := &SessionConfig{} diff --git a/go/types.go b/go/types.go index 82780e149..30216adf0 100644 --- a/go/types.go +++ b/go/types.go @@ -904,6 +904,38 @@ type SessionConfig struct { // Custom instruction files (.github/copilot-instructions.md, AGENTS.md, etc.) are // always loaded from the working directory regardless of this setting. EnableConfigDiscovery bool + // SkipEmbeddingRetrieval, when non-nil, controls embedding-based retrieval + // for this session. Use in multitenant deployments to prevent cross-session + // information leakage through the shared embedding cache. + SkipEmbeddingRetrieval *bool + // EmbeddingCacheStorage controls how the embedding cache is stored for this session. + // "persistent" caches on disk and shares across sessions/restarts. + // "in-memory" caches in memory only and discards when the session ends. + EmbeddingCacheStorage *string + // OrganizationCustomInstructions provides organization-level custom instructions + // to include in the system prompt. Allows hosts to inject organization-specific + // guidance without relying on filesystem-based instruction discovery. + OrganizationCustomInstructions *string + // EnableOnDemandInstructionDiscovery, when non-nil, controls on-demand discovery + // of instruction files (AGENTS.md, .github/copilot-instructions.md, etc.) after + // successful file views. + EnableOnDemandInstructionDiscovery *bool + // EnableFileHooks, when non-nil, controls loading of file-based hooks from + // .github/hooks/. This is separate from the Hooks callback parameter which + // gates SDK hook event registration. + EnableFileHooks *bool + // EnableHostGitOperations, when non-nil, controls git operations on the host + // filesystem (branch detection, file status, commit history). When false, no + // git context is surfaced in the system prompt. + EnableHostGitOperations *bool + // EnableSessionStore, when non-nil, controls the cross-session store for search + // and retrieval. When false, session content is not written to or read from the + // shared session store. + EnableSessionStore *bool + // EnableSkills, when non-nil, controls skill loading (including builtin skills + // and discovered skill directories). When false, no skills are loaded regardless + // of SkillDirectories or EnableConfigDiscovery settings. + EnableSkills *bool // Tools exposes caller-implemented tools to the CLI. A Tool with a nil Handler // is declaration-only; the consumer must resolve its calls via pending tool RPCs. Tools []Tool @@ -966,9 +998,7 @@ type SessionConfig struct { // MCPServers configures MCP servers for the session MCPServers map[string]MCPServerConfig // MCPOAuthTokenStorage controls how MCP OAuth tokens are stored for this session. - // "persistent" stores tokens in the OS keychain (shared across sessions). - // "in-memory" stores tokens in memory and discards them when the session ends. - // Defaults to "in-memory" for safe multitenant behavior. + // When empty, the runtime default ("in-memory") is used. MCPOAuthTokenStorage string // CustomAgents configures custom agents for the session CustomAgents []CustomAgentConfig @@ -1278,6 +1308,32 @@ type ResumeSessionConfig struct { // Custom instruction files (.github/copilot-instructions.md, AGENTS.md, etc.) are // always loaded from the working directory regardless of this setting. EnableConfigDiscovery bool + // SkipEmbeddingRetrieval, when non-nil, controls embedding-based retrieval + // for this session. Use in multitenant deployments to prevent cross-session + // information leakage through the shared embedding cache. + SkipEmbeddingRetrieval *bool + // EmbeddingCacheStorage controls how the embedding cache is stored for this session. + // "persistent" caches on disk and shares across sessions/restarts. + // "in-memory" caches in memory only and discards when the session ends. + EmbeddingCacheStorage *string + // OrganizationCustomInstructions provides organization-level custom instructions + // to include in the system prompt. + OrganizationCustomInstructions *string + // EnableOnDemandInstructionDiscovery, when non-nil, controls on-demand discovery + // of instruction files after successful file views. + EnableOnDemandInstructionDiscovery *bool + // EnableFileHooks, when non-nil, controls loading of file-based hooks from + // .github/hooks/. This is separate from the Hooks callback parameter which + // gates SDK hook event registration. + EnableFileHooks *bool + // EnableHostGitOperations, when non-nil, controls git operations on the host + // filesystem. + EnableHostGitOperations *bool + // EnableSessionStore, when non-nil, controls the cross-session store for search + // and retrieval across sessions. + EnableSessionStore *bool + // EnableSkills, when non-nil, controls skill loading. + EnableSkills *bool // Streaming enables streaming of assistant message and reasoning chunks. // When non-nil and true, assistant.message_delta and assistant.reasoning_delta // events with deltaContent are sent as the response is generated. @@ -1293,9 +1349,7 @@ type ResumeSessionConfig struct { // MCPServers configures MCP servers for the session MCPServers map[string]MCPServerConfig // MCPOAuthTokenStorage controls how MCP OAuth tokens are stored for this session. - // "persistent" stores tokens in the OS keychain (shared across sessions). - // "in-memory" stores tokens in memory and discards them when the session ends. - // Defaults to "in-memory" for safe multitenant behavior. + // When empty, the runtime default ("in-memory") is used. MCPOAuthTokenStorage string // CustomAgents configures custom agents for the session CustomAgents []CustomAgentConfig @@ -1586,57 +1640,65 @@ type SessionLifecycleHandler func(event SessionLifecycleEvent) // createSessionRequest is the request for session.create type createSessionRequest struct { - Model string `json:"model,omitempty"` - SessionID string `json:"sessionId,omitempty"` - ClientName string `json:"clientName,omitempty"` - ReasoningEffort string `json:"reasoningEffort,omitempty"` - ReasoningSummary ReasoningSummary `json:"reasoningSummary,omitempty"` - Tools []Tool `json:"tools,omitempty"` - SystemMessage *SystemMessageConfig `json:"systemMessage,omitempty"` - AvailableTools []string `json:"availableTools"` - ExcludedTools []string `json:"excludedTools,omitempty"` - ToolFilterPrecedence *rpc.OptionsUpdateToolFilterPrecedence `json:"toolFilterPrecedence,omitempty"` - Provider *ProviderConfig `json:"provider,omitempty"` - EnableSessionTelemetry *bool `json:"enableSessionTelemetry,omitempty"` - SkipCustomInstructions *bool `json:"skipCustomInstructions,omitempty"` - CustomAgentsLocalOnly *bool `json:"customAgentsLocalOnly,omitempty"` - CoauthorEnabled *bool `json:"coauthorEnabled,omitempty"` - ManageScheduleEnabled *bool `json:"manageScheduleEnabled,omitempty"` - ModelCapabilities *rpc.ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"` - RequestPermission *bool `json:"requestPermission,omitempty"` - RequestUserInput *bool `json:"requestUserInput,omitempty"` - RequestExitPlanMode *bool `json:"requestExitPlanMode,omitempty"` - RequestAutoModeSwitch *bool `json:"requestAutoModeSwitch,omitempty"` - Hooks *bool `json:"hooks,omitempty"` - WorkingDirectory string `json:"workingDirectory,omitempty"` - Streaming *bool `json:"streaming,omitempty"` - IncludeSubAgentStreamingEvents *bool `json:"includeSubAgentStreamingEvents,omitempty"` - MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"` - MCPOAuthTokenStorage string `json:"mcpOAuthTokenStorage,omitempty"` - EnvValueMode string `json:"envValueMode,omitempty"` - CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"` - DefaultAgent *DefaultAgentConfig `json:"defaultAgent,omitempty"` - Agent string `json:"agent,omitempty"` - ConfigDir string `json:"configDir,omitempty"` - EnableConfigDiscovery *bool `json:"enableConfigDiscovery,omitempty"` - SkillDirectories []string `json:"skillDirectories,omitempty"` - PluginDirectories []string `json:"pluginDirectories,omitempty"` - InstructionDirectories []string `json:"instructionDirectories,omitempty"` - DisabledSkills []string `json:"disabledSkills,omitempty"` - InfiniteSessions *InfiniteSessionConfig `json:"infiniteSessions,omitempty"` - LargeOutput *LargeToolOutputConfig `json:"largeOutput,omitempty"` - Commands []wireCommand `json:"commands,omitempty"` - RequestElicitation *bool `json:"requestElicitation,omitempty"` - RequestMcpApps *bool `json:"requestMcpApps,omitempty"` - GitHubToken string `json:"gitHubToken,omitempty"` - RemoteSession rpc.RemoteSessionMode `json:"remoteSession,omitempty"` - Cloud *CloudSessionOptions `json:"cloud,omitempty"` - Canvases []CanvasDeclaration `json:"canvases,omitempty"` - RequestCanvasRenderer *bool `json:"requestCanvasRenderer,omitempty"` - RequestExtensions *bool `json:"requestExtensions,omitempty"` - ExtensionInfo *ExtensionInfo `json:"extensionInfo,omitempty"` - Traceparent string `json:"traceparent,omitempty"` - Tracestate string `json:"tracestate,omitempty"` + Model string `json:"model,omitempty"` + SessionID string `json:"sessionId,omitempty"` + ClientName string `json:"clientName,omitempty"` + ReasoningEffort string `json:"reasoningEffort,omitempty"` + ReasoningSummary ReasoningSummary `json:"reasoningSummary,omitempty"` + Tools []Tool `json:"tools,omitempty"` + SystemMessage *SystemMessageConfig `json:"systemMessage,omitempty"` + AvailableTools []string `json:"availableTools"` + ExcludedTools []string `json:"excludedTools,omitempty"` + ToolFilterPrecedence *rpc.OptionsUpdateToolFilterPrecedence `json:"toolFilterPrecedence,omitempty"` + Provider *ProviderConfig `json:"provider,omitempty"` + EnableSessionTelemetry *bool `json:"enableSessionTelemetry,omitempty"` + SkipCustomInstructions *bool `json:"skipCustomInstructions,omitempty"` + CustomAgentsLocalOnly *bool `json:"customAgentsLocalOnly,omitempty"` + CoauthorEnabled *bool `json:"coauthorEnabled,omitempty"` + ManageScheduleEnabled *bool `json:"manageScheduleEnabled,omitempty"` + ModelCapabilities *rpc.ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"` + RequestPermission *bool `json:"requestPermission,omitempty"` + RequestUserInput *bool `json:"requestUserInput,omitempty"` + RequestExitPlanMode *bool `json:"requestExitPlanMode,omitempty"` + RequestAutoModeSwitch *bool `json:"requestAutoModeSwitch,omitempty"` + Hooks *bool `json:"hooks,omitempty"` + WorkingDirectory string `json:"workingDirectory,omitempty"` + Streaming *bool `json:"streaming,omitempty"` + IncludeSubAgentStreamingEvents *bool `json:"includeSubAgentStreamingEvents,omitempty"` + MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"` + MCPOAuthTokenStorage string `json:"mcpOAuthTokenStorage,omitempty"` + EnvValueMode string `json:"envValueMode,omitempty"` + CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"` + DefaultAgent *DefaultAgentConfig `json:"defaultAgent,omitempty"` + Agent string `json:"agent,omitempty"` + ConfigDir string `json:"configDir,omitempty"` + EnableConfigDiscovery *bool `json:"enableConfigDiscovery,omitempty"` + SkipEmbeddingRetrieval *bool `json:"skipEmbeddingRetrieval,omitempty"` + EmbeddingCacheStorage *string `json:"embeddingCacheStorage,omitempty"` + OrganizationCustomInstructions *string `json:"organizationCustomInstructions,omitempty"` + EnableOnDemandInstructionDiscovery *bool `json:"enableOnDemandInstructionDiscovery,omitempty"` + EnableFileHooks *bool `json:"enableFileHooks,omitempty"` + EnableHostGitOperations *bool `json:"enableHostGitOperations,omitempty"` + EnableSessionStore *bool `json:"enableSessionStore,omitempty"` + EnableSkills *bool `json:"enableSkills,omitempty"` + SkillDirectories []string `json:"skillDirectories,omitempty"` + PluginDirectories []string `json:"pluginDirectories,omitempty"` + InstructionDirectories []string `json:"instructionDirectories,omitempty"` + DisabledSkills []string `json:"disabledSkills,omitempty"` + InfiniteSessions *InfiniteSessionConfig `json:"infiniteSessions,omitempty"` + LargeOutput *LargeToolOutputConfig `json:"largeOutput,omitempty"` + Commands []wireCommand `json:"commands,omitempty"` + RequestElicitation *bool `json:"requestElicitation,omitempty"` + RequestMcpApps *bool `json:"requestMcpApps,omitempty"` + GitHubToken string `json:"gitHubToken,omitempty"` + RemoteSession rpc.RemoteSessionMode `json:"remoteSession,omitempty"` + Cloud *CloudSessionOptions `json:"cloud,omitempty"` + Canvases []CanvasDeclaration `json:"canvases,omitempty"` + RequestCanvasRenderer *bool `json:"requestCanvasRenderer,omitempty"` + RequestExtensions *bool `json:"requestExtensions,omitempty"` + ExtensionInfo *ExtensionInfo `json:"extensionInfo,omitempty"` + Traceparent string `json:"traceparent,omitempty"` + Tracestate string `json:"tracestate,omitempty"` } // wireCommand is the wire representation of a command (name + description only, no handler). @@ -1654,59 +1716,67 @@ type createSessionResponse struct { // resumeSessionRequest is the request for session.resume type resumeSessionRequest struct { - SessionID string `json:"sessionId"` - ClientName string `json:"clientName,omitempty"` - Model string `json:"model,omitempty"` - ReasoningEffort string `json:"reasoningEffort,omitempty"` - ReasoningSummary ReasoningSummary `json:"reasoningSummary,omitempty"` - Tools []Tool `json:"tools,omitempty"` - SystemMessage *SystemMessageConfig `json:"systemMessage,omitempty"` - AvailableTools []string `json:"availableTools"` - ExcludedTools []string `json:"excludedTools,omitempty"` - ToolFilterPrecedence *rpc.OptionsUpdateToolFilterPrecedence `json:"toolFilterPrecedence,omitempty"` - Provider *ProviderConfig `json:"provider,omitempty"` - EnableSessionTelemetry *bool `json:"enableSessionTelemetry,omitempty"` - SkipCustomInstructions *bool `json:"skipCustomInstructions,omitempty"` - CustomAgentsLocalOnly *bool `json:"customAgentsLocalOnly,omitempty"` - CoauthorEnabled *bool `json:"coauthorEnabled,omitempty"` - ManageScheduleEnabled *bool `json:"manageScheduleEnabled,omitempty"` - ModelCapabilities *rpc.ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"` - RequestPermission *bool `json:"requestPermission,omitempty"` - RequestUserInput *bool `json:"requestUserInput,omitempty"` - RequestExitPlanMode *bool `json:"requestExitPlanMode,omitempty"` - RequestAutoModeSwitch *bool `json:"requestAutoModeSwitch,omitempty"` - Hooks *bool `json:"hooks,omitempty"` - WorkingDirectory string `json:"workingDirectory,omitempty"` - ConfigDir string `json:"configDir,omitempty"` - EnableConfigDiscovery *bool `json:"enableConfigDiscovery,omitempty"` - DisableResume *bool `json:"disableResume,omitempty"` - ContinuePendingWork *bool `json:"continuePendingWork,omitempty"` - Streaming *bool `json:"streaming,omitempty"` - IncludeSubAgentStreamingEvents *bool `json:"includeSubAgentStreamingEvents,omitempty"` - MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"` - MCPOAuthTokenStorage string `json:"mcpOAuthTokenStorage,omitempty"` - EnvValueMode string `json:"envValueMode,omitempty"` - CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"` - DefaultAgent *DefaultAgentConfig `json:"defaultAgent,omitempty"` - Agent string `json:"agent,omitempty"` - SkillDirectories []string `json:"skillDirectories,omitempty"` - PluginDirectories []string `json:"pluginDirectories,omitempty"` - InstructionDirectories []string `json:"instructionDirectories,omitempty"` - DisabledSkills []string `json:"disabledSkills,omitempty"` - InfiniteSessions *InfiniteSessionConfig `json:"infiniteSessions,omitempty"` - LargeOutput *LargeToolOutputConfig `json:"largeOutput,omitempty"` - Commands []wireCommand `json:"commands,omitempty"` - RequestElicitation *bool `json:"requestElicitation,omitempty"` - RequestMcpApps *bool `json:"requestMcpApps,omitempty"` - GitHubToken string `json:"gitHubToken,omitempty"` - RemoteSession rpc.RemoteSessionMode `json:"remoteSession,omitempty"` - Canvases []CanvasDeclaration `json:"canvases,omitempty"` - OpenCanvases []rpc.OpenCanvasInstance `json:"openCanvases,omitempty"` - RequestCanvasRenderer *bool `json:"requestCanvasRenderer,omitempty"` - RequestExtensions *bool `json:"requestExtensions,omitempty"` - ExtensionInfo *ExtensionInfo `json:"extensionInfo,omitempty"` - Traceparent string `json:"traceparent,omitempty"` - Tracestate string `json:"tracestate,omitempty"` + SessionID string `json:"sessionId"` + ClientName string `json:"clientName,omitempty"` + Model string `json:"model,omitempty"` + ReasoningEffort string `json:"reasoningEffort,omitempty"` + ReasoningSummary ReasoningSummary `json:"reasoningSummary,omitempty"` + Tools []Tool `json:"tools,omitempty"` + SystemMessage *SystemMessageConfig `json:"systemMessage,omitempty"` + AvailableTools []string `json:"availableTools"` + ExcludedTools []string `json:"excludedTools,omitempty"` + ToolFilterPrecedence *rpc.OptionsUpdateToolFilterPrecedence `json:"toolFilterPrecedence,omitempty"` + Provider *ProviderConfig `json:"provider,omitempty"` + EnableSessionTelemetry *bool `json:"enableSessionTelemetry,omitempty"` + SkipCustomInstructions *bool `json:"skipCustomInstructions,omitempty"` + CustomAgentsLocalOnly *bool `json:"customAgentsLocalOnly,omitempty"` + CoauthorEnabled *bool `json:"coauthorEnabled,omitempty"` + ManageScheduleEnabled *bool `json:"manageScheduleEnabled,omitempty"` + ModelCapabilities *rpc.ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"` + RequestPermission *bool `json:"requestPermission,omitempty"` + RequestUserInput *bool `json:"requestUserInput,omitempty"` + RequestExitPlanMode *bool `json:"requestExitPlanMode,omitempty"` + RequestAutoModeSwitch *bool `json:"requestAutoModeSwitch,omitempty"` + Hooks *bool `json:"hooks,omitempty"` + WorkingDirectory string `json:"workingDirectory,omitempty"` + ConfigDir string `json:"configDir,omitempty"` + EnableConfigDiscovery *bool `json:"enableConfigDiscovery,omitempty"` + SkipEmbeddingRetrieval *bool `json:"skipEmbeddingRetrieval,omitempty"` + EmbeddingCacheStorage *string `json:"embeddingCacheStorage,omitempty"` + OrganizationCustomInstructions *string `json:"organizationCustomInstructions,omitempty"` + EnableOnDemandInstructionDiscovery *bool `json:"enableOnDemandInstructionDiscovery,omitempty"` + EnableFileHooks *bool `json:"enableFileHooks,omitempty"` + EnableHostGitOperations *bool `json:"enableHostGitOperations,omitempty"` + EnableSessionStore *bool `json:"enableSessionStore,omitempty"` + EnableSkills *bool `json:"enableSkills,omitempty"` + DisableResume *bool `json:"disableResume,omitempty"` + ContinuePendingWork *bool `json:"continuePendingWork,omitempty"` + Streaming *bool `json:"streaming,omitempty"` + IncludeSubAgentStreamingEvents *bool `json:"includeSubAgentStreamingEvents,omitempty"` + MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"` + MCPOAuthTokenStorage string `json:"mcpOAuthTokenStorage,omitempty"` + EnvValueMode string `json:"envValueMode,omitempty"` + CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"` + DefaultAgent *DefaultAgentConfig `json:"defaultAgent,omitempty"` + Agent string `json:"agent,omitempty"` + SkillDirectories []string `json:"skillDirectories,omitempty"` + PluginDirectories []string `json:"pluginDirectories,omitempty"` + InstructionDirectories []string `json:"instructionDirectories,omitempty"` + DisabledSkills []string `json:"disabledSkills,omitempty"` + InfiniteSessions *InfiniteSessionConfig `json:"infiniteSessions,omitempty"` + LargeOutput *LargeToolOutputConfig `json:"largeOutput,omitempty"` + Commands []wireCommand `json:"commands,omitempty"` + RequestElicitation *bool `json:"requestElicitation,omitempty"` + RequestMcpApps *bool `json:"requestMcpApps,omitempty"` + GitHubToken string `json:"gitHubToken,omitempty"` + RemoteSession rpc.RemoteSessionMode `json:"remoteSession,omitempty"` + Canvases []CanvasDeclaration `json:"canvases,omitempty"` + OpenCanvases []rpc.OpenCanvasInstance `json:"openCanvases,omitempty"` + RequestCanvasRenderer *bool `json:"requestCanvasRenderer,omitempty"` + RequestExtensions *bool `json:"requestExtensions,omitempty"` + ExtensionInfo *ExtensionInfo `json:"extensionInfo,omitempty"` + Traceparent string `json:"traceparent,omitempty"` + Tracestate string `json:"tracestate,omitempty"` } // resumeSessionResponse is the response from session.resume diff --git a/java/src/main/java/com/github/copilot/CopilotClient.java b/java/src/main/java/com/github/copilot/CopilotClient.java index 471a4bcd7..d8c8f9cdb 100644 --- a/java/src/main/java/com/github/copilot/CopilotClient.java +++ b/java/src/main/java/com/github/copilot/CopilotClient.java @@ -512,6 +512,27 @@ public CompletableFuture createSession(SessionConfig config) { + "the tools it wants — e.g. setAvailableTools(new ToolSet().addBuiltIn(BuiltInTools.ISOLATED))."); } request.setToolFilterPrecedence("excluded"); + if (request.getSkipEmbeddingRetrieval() == null) { + request.setSkipEmbeddingRetrieval(true); + } + if (request.getEmbeddingCacheStorage() == null) { + request.setEmbeddingCacheStorage("in-memory"); + } + if (request.getEnableOnDemandInstructionDiscovery() == null) { + request.setEnableOnDemandInstructionDiscovery(false); + } + if (request.getEnableFileHooks() == null) { + request.setEnableFileHooks(false); + } + if (request.getEnableHostGitOperations() == null) { + request.setEnableHostGitOperations(false); + } + if (request.getEnableSessionStore() == null) { + request.setEnableSessionStore(false); + } + if (request.getEnableSkills() == null) { + request.setEnableSkills(false); + } if (request.getMcpOAuthTokenStorage() == null) { request.setMcpOAuthTokenStorage("in-memory"); } @@ -629,6 +650,27 @@ public CompletableFuture resumeSession(String sessionId, ResumeS + "the tools it wants — e.g. setAvailableTools(new ToolSet().addBuiltIn(BuiltInTools.ISOLATED))."); } request.setToolFilterPrecedence("excluded"); + if (request.getSkipEmbeddingRetrieval() == null) { + request.setSkipEmbeddingRetrieval(true); + } + if (request.getEmbeddingCacheStorage() == null) { + request.setEmbeddingCacheStorage("in-memory"); + } + if (request.getEnableOnDemandInstructionDiscovery() == null) { + request.setEnableOnDemandInstructionDiscovery(false); + } + if (request.getEnableFileHooks() == null) { + request.setEnableFileHooks(false); + } + if (request.getEnableHostGitOperations() == null) { + request.setEnableHostGitOperations(false); + } + if (request.getEnableSessionStore() == null) { + request.setEnableSessionStore(false); + } + if (request.getEnableSkills() == null) { + request.setEnableSkills(false); + } if (request.getMcpOAuthTokenStorage() == null) { request.setMcpOAuthTokenStorage("in-memory"); } diff --git a/java/src/main/java/com/github/copilot/SessionRequestBuilder.java b/java/src/main/java/com/github/copilot/SessionRequestBuilder.java index 52f4e2179..bcd08bf5d 100644 --- a/java/src/main/java/com/github/copilot/SessionRequestBuilder.java +++ b/java/src/main/java/com/github/copilot/SessionRequestBuilder.java @@ -137,6 +137,18 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess request.setDisabledSkills(config.getDisabledSkills()); request.setConfigDirectory(config.getConfigDirectory()); config.getEnableConfigDiscovery().ifPresent(request::setEnableConfigDiscovery); + config.getSkipEmbeddingRetrieval().ifPresent(request::setSkipEmbeddingRetrieval); + if (config.getOrganizationCustomInstructions() != null) { + request.setOrganizationCustomInstructions(config.getOrganizationCustomInstructions()); + } + config.getEnableOnDemandInstructionDiscovery().ifPresent(request::setEnableOnDemandInstructionDiscovery); + config.getEnableFileHooks().ifPresent(request::setEnableFileHooks); + config.getEnableHostGitOperations().ifPresent(request::setEnableHostGitOperations); + config.getEnableSessionStore().ifPresent(request::setEnableSessionStore); + config.getEnableSkills().ifPresent(request::setEnableSkills); + if (config.getEmbeddingCacheStorage() != null) { + request.setEmbeddingCacheStorage(config.getEmbeddingCacheStorage()); + } request.setModelCapabilities(config.getModelCapabilities()); if (config.getCommands() != null && !config.getCommands().isEmpty()) { @@ -220,6 +232,18 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo request.setWorkingDirectory(config.getWorkingDirectory()); request.setConfigDirectory(config.getConfigDirectory()); config.getEnableConfigDiscovery().ifPresent(request::setEnableConfigDiscovery); + config.getSkipEmbeddingRetrieval().ifPresent(request::setSkipEmbeddingRetrieval); + if (config.getOrganizationCustomInstructions() != null) { + request.setOrganizationCustomInstructions(config.getOrganizationCustomInstructions()); + } + config.getEnableOnDemandInstructionDiscovery().ifPresent(request::setEnableOnDemandInstructionDiscovery); + config.getEnableFileHooks().ifPresent(request::setEnableFileHooks); + config.getEnableHostGitOperations().ifPresent(request::setEnableHostGitOperations); + config.getEnableSessionStore().ifPresent(request::setEnableSessionStore); + config.getEnableSkills().ifPresent(request::setEnableSkills); + if (config.getEmbeddingCacheStorage() != null) { + request.setEmbeddingCacheStorage(config.getEmbeddingCacheStorage()); + } if (config.isDisableResume()) { request.setDisableResume(true); } diff --git a/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java b/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java index b8dddbec7..c5dc200f9 100644 --- a/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java +++ b/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java @@ -120,6 +120,38 @@ public final class CreateSessionRequest { @JsonProperty("enableConfigDiscovery") private Boolean enableConfigDiscovery; + @JsonProperty("skipEmbeddingRetrieval") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean skipEmbeddingRetrieval; + + @JsonProperty("organizationCustomInstructions") + @JsonInclude(JsonInclude.Include.NON_NULL) + private String organizationCustomInstructions; + + @JsonProperty("enableOnDemandInstructionDiscovery") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean enableOnDemandInstructionDiscovery; + + @JsonProperty("enableFileHooks") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean enableFileHooks; + + @JsonProperty("enableHostGitOperations") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean enableHostGitOperations; + + @JsonProperty("enableSessionStore") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean enableSessionStore; + + @JsonProperty("enableSkills") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean enableSkills; + + @JsonProperty("embeddingCacheStorage") + @JsonInclude(JsonInclude.Include.NON_NULL) + private String embeddingCacheStorage; + @JsonProperty("commands") private List commands; @@ -516,6 +548,141 @@ public void clearEnableConfigDiscovery() { this.enableConfigDiscovery = null; } + /** Gets skip embedding retrieval flag. @return the flag */ + public Boolean getSkipEmbeddingRetrieval() { + return skipEmbeddingRetrieval; + } + + /** + * Sets skip embedding retrieval flag. @param skipEmbeddingRetrieval the flag + */ + public void setSkipEmbeddingRetrieval(boolean skipEmbeddingRetrieval) { + this.skipEmbeddingRetrieval = skipEmbeddingRetrieval; + } + + /** + * Clears the skipEmbeddingRetrieval setting, reverting to the default behavior. + */ + public void clearSkipEmbeddingRetrieval() { + this.skipEmbeddingRetrieval = null; + } + + /** Gets organization custom instructions. @return the instructions */ + public String getOrganizationCustomInstructions() { + return organizationCustomInstructions; + } + + /** + * Sets organization custom instructions. @param organizationCustomInstructions + * the instructions + */ + public void setOrganizationCustomInstructions(String organizationCustomInstructions) { + this.organizationCustomInstructions = organizationCustomInstructions; + } + + /** Gets enable on-demand instruction discovery flag. @return the flag */ + public Boolean getEnableOnDemandInstructionDiscovery() { + return enableOnDemandInstructionDiscovery; + } + + /** + * Sets enable on-demand instruction discovery flag. @param + * enableOnDemandInstructionDiscovery the flag + */ + public void setEnableOnDemandInstructionDiscovery(boolean enableOnDemandInstructionDiscovery) { + this.enableOnDemandInstructionDiscovery = enableOnDemandInstructionDiscovery; + } + + /** + * Clears the enableOnDemandInstructionDiscovery setting, reverting to the + * default behavior. + */ + public void clearEnableOnDemandInstructionDiscovery() { + this.enableOnDemandInstructionDiscovery = null; + } + + /** Gets enable file hooks flag. @return the flag */ + public Boolean getEnableFileHooks() { + return enableFileHooks; + } + + /** Sets enable file hooks flag. @param enableFileHooks the flag */ + public void setEnableFileHooks(boolean enableFileHooks) { + this.enableFileHooks = enableFileHooks; + } + + /** Clears the enableFileHooks setting, reverting to the default behavior. */ + public void clearEnableFileHooks() { + this.enableFileHooks = null; + } + + /** Gets enable host git operations flag. @return the flag */ + public Boolean getEnableHostGitOperations() { + return enableHostGitOperations; + } + + /** + * Sets enable host git operations flag. @param enableHostGitOperations the flag + */ + public void setEnableHostGitOperations(boolean enableHostGitOperations) { + this.enableHostGitOperations = enableHostGitOperations; + } + + /** + * Clears the enableHostGitOperations setting, reverting to the default + * behavior. + */ + public void clearEnableHostGitOperations() { + this.enableHostGitOperations = null; + } + + /** Gets enable session store flag. @return the flag */ + public Boolean getEnableSessionStore() { + return enableSessionStore; + } + + /** Sets enable session store flag. @param enableSessionStore the flag */ + public void setEnableSessionStore(boolean enableSessionStore) { + this.enableSessionStore = enableSessionStore; + } + + /** Clears the enableSessionStore setting, reverting to the default behavior. */ + public void clearEnableSessionStore() { + this.enableSessionStore = null; + } + + /** Gets enable skills flag. @return the flag */ + public Boolean getEnableSkills() { + return enableSkills; + } + + /** Sets enable skills flag. @param enableSkills the flag */ + public void setEnableSkills(boolean enableSkills) { + this.enableSkills = enableSkills; + } + + /** Clears the enableSkills setting, reverting to the default behavior. */ + public void clearEnableSkills() { + this.enableSkills = null; + } + + /** Gets embedding cache storage mode. @return the mode */ + public String getEmbeddingCacheStorage() { + return embeddingCacheStorage; + } + + /** Sets embedding cache storage mode. @param embeddingCacheStorage the mode */ + public void setEmbeddingCacheStorage(String embeddingCacheStorage) { + this.embeddingCacheStorage = embeddingCacheStorage; + } + + /** + * Clears the embeddingCacheStorage setting, reverting to the default behavior. + */ + public void clearEmbeddingCacheStorage() { + this.embeddingCacheStorage = null; + } + /** Gets include sub-agent streaming events flag. @return the flag */ public Boolean getIncludeSubAgentStreamingEvents() { return includeSubAgentStreamingEvents; diff --git a/java/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java b/java/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java index 3cdf19182..3510cc7fd 100644 --- a/java/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java +++ b/java/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java @@ -59,6 +59,14 @@ public class ResumeSessionConfig { private String workingDirectory; private String configDirectory; private Boolean enableConfigDiscovery; + private Boolean skipEmbeddingRetrieval; + private String organizationCustomInstructions; + private Boolean enableOnDemandInstructionDiscovery; + private Boolean enableFileHooks; + private Boolean enableHostGitOperations; + private Boolean enableSessionStore; + private Boolean enableSkills; + private String embeddingCacheStorage; private boolean disableResume; private boolean streaming; private Boolean includeSubAgentStreamingEvents; @@ -609,8 +617,9 @@ public ResumeSessionConfig setConfigDirectory(String configDirectory) { /** * Gets whether automatic configuration discovery is enabled. * - * @return {@code true} to enable discovery, {@code false} to disable, or - * {@code null} to use the runtime default + * @return an {@link java.util.Optional} containing {@code true} to enable + * discovery or {@code false} to disable it, or + * {@link java.util.Optional#empty()} to use the default behavior */ @JsonIgnore public Optional getEnableConfigDiscovery() { @@ -626,8 +635,7 @@ public Optional getEnableConfigDiscovery() { * explicitly provided configurations. * * @param enableConfigDiscovery - * {@code true} to enable discovery, {@code false} to disable, or - * {@code null} to use the runtime default + * {@code true} to enable discovery, {@code false} to disable * @return this config for method chaining */ public ResumeSessionConfig setEnableConfigDiscovery(boolean enableConfigDiscovery) { @@ -645,11 +653,276 @@ public ResumeSessionConfig clearEnableConfigDiscovery() { return this; } + /** + * Gets whether embedding-based retrieval is skipped. + * + * @return an {@link java.util.Optional} containing {@code true} to skip + * embedding retrieval or {@code false} to force it, or + * {@link java.util.Optional#empty()} to use the default behavior + */ + @JsonIgnore + public Optional getSkipEmbeddingRetrieval() { + return Optional.ofNullable(skipEmbeddingRetrieval); + } + + /** + * Sets whether to skip embedding-based retrieval. + * + * @param skipEmbeddingRetrieval + * {@code true} to skip embedding retrieval, {@code false} to keep it + * enabled + * @return this config for method chaining + */ + public ResumeSessionConfig setSkipEmbeddingRetrieval(boolean skipEmbeddingRetrieval) { + this.skipEmbeddingRetrieval = skipEmbeddingRetrieval; + return this; + } + + /** + * Clears the skipEmbeddingRetrieval setting, reverting to the default behavior. + * + * @return this instance for method chaining + */ + public ResumeSessionConfig clearSkipEmbeddingRetrieval() { + this.skipEmbeddingRetrieval = null; + return this; + } + + /** + * Gets the organization-level custom instructions. + * + * @return the organization-level custom instructions, or {@code null} if not + * set + */ + public String getOrganizationCustomInstructions() { + return organizationCustomInstructions; + } + + /** + * Sets organization-level custom instructions. + * + * @param organizationCustomInstructions + * the organization-level custom instructions + * @return this config for method chaining + */ + public ResumeSessionConfig setOrganizationCustomInstructions(String organizationCustomInstructions) { + this.organizationCustomInstructions = organizationCustomInstructions; + return this; + } + + /** + * Gets whether on-demand instruction file discovery is enabled. + * + * @return an {@link java.util.Optional} containing {@code true} to enable + * on-demand discovery or {@code false} to disable it, or + * {@link java.util.Optional#empty()} to use the default behavior + */ + @JsonIgnore + public Optional getEnableOnDemandInstructionDiscovery() { + return Optional.ofNullable(enableOnDemandInstructionDiscovery); + } + + /** + * Sets whether instruction files are discovered on demand. + * + * @param enableOnDemandInstructionDiscovery + * {@code true} to enable on-demand instruction discovery, + * {@code false} to disable it + * @return this config for method chaining + */ + public ResumeSessionConfig setEnableOnDemandInstructionDiscovery(boolean enableOnDemandInstructionDiscovery) { + this.enableOnDemandInstructionDiscovery = enableOnDemandInstructionDiscovery; + return this; + } + + /** + * Clears the enableOnDemandInstructionDiscovery setting, reverting to the + * default behavior. + * + * @return this instance for method chaining + */ + public ResumeSessionConfig clearEnableOnDemandInstructionDiscovery() { + this.enableOnDemandInstructionDiscovery = null; + return this; + } + + /** + * Gets whether file-based hooks are enabled. + * + * @return an {@link java.util.Optional} containing {@code true} to enable file + * hooks or {@code false} to disable them, or + * {@link java.util.Optional#empty()} to use the default behavior + */ + @JsonIgnore + public Optional getEnableFileHooks() { + return Optional.ofNullable(enableFileHooks); + } + + /** + * Sets whether file-based hooks from {@code .github/hooks/} are enabled. + * + * @param enableFileHooks + * {@code true} to enable file hooks, {@code false} to disable them + * @return this config for method chaining + */ + public ResumeSessionConfig setEnableFileHooks(boolean enableFileHooks) { + this.enableFileHooks = enableFileHooks; + return this; + } + + /** + * Clears the enableFileHooks setting, reverting to the default behavior. + * + * @return this instance for method chaining + */ + public ResumeSessionConfig clearEnableFileHooks() { + this.enableFileHooks = null; + return this; + } + + /** + * Gets whether host git operations are enabled. + * + * @return an {@link java.util.Optional} containing {@code true} to enable host + * git operations or {@code false} to disable them, or + * {@link java.util.Optional#empty()} to use the default behavior + */ + @JsonIgnore + public Optional getEnableHostGitOperations() { + return Optional.ofNullable(enableHostGitOperations); + } + + /** + * Sets whether git operations on the host filesystem are enabled. + * + * @param enableHostGitOperations + * {@code true} to enable host git operations, {@code false} to + * disable them + * @return this config for method chaining + */ + public ResumeSessionConfig setEnableHostGitOperations(boolean enableHostGitOperations) { + this.enableHostGitOperations = enableHostGitOperations; + return this; + } + + /** + * Clears the enableHostGitOperations setting, reverting to the default + * behavior. + * + * @return this instance for method chaining + */ + public ResumeSessionConfig clearEnableHostGitOperations() { + this.enableHostGitOperations = null; + return this; + } + + /** + * Gets whether the cross-session store is enabled. + * + * @return an {@link java.util.Optional} containing {@code true} to enable the + * session store or {@code false} to disable it, or + * {@link java.util.Optional#empty()} to use the default behavior + */ + @JsonIgnore + public Optional getEnableSessionStore() { + return Optional.ofNullable(enableSessionStore); + } + + /** + * Sets whether the cross-session store is enabled. + * + * @param enableSessionStore + * {@code true} to enable the session store, {@code false} to disable + * it + * @return this config for method chaining + */ + public ResumeSessionConfig setEnableSessionStore(boolean enableSessionStore) { + this.enableSessionStore = enableSessionStore; + return this; + } + + /** + * Clears the enableSessionStore setting, reverting to the default behavior. + * + * @return this instance for method chaining + */ + public ResumeSessionConfig clearEnableSessionStore() { + this.enableSessionStore = null; + return this; + } + + /** + * Gets whether skill loading is enabled. + * + * @return an {@link java.util.Optional} containing {@code true} to enable skill + * loading or {@code false} to disable it, or + * {@link java.util.Optional#empty()} to use the default behavior + */ + @JsonIgnore + public Optional getEnableSkills() { + return Optional.ofNullable(enableSkills); + } + + /** + * Sets whether skill loading is enabled. + * + * @param enableSkills + * {@code true} to enable skill loading, {@code false} to disable it + * @return this config for method chaining + */ + public ResumeSessionConfig setEnableSkills(boolean enableSkills) { + this.enableSkills = enableSkills; + return this; + } + + /** + * Clears the enableSkills setting, reverting to the default behavior. + * + * @return this instance for method chaining + */ + public ResumeSessionConfig clearEnableSkills() { + this.enableSkills = null; + return this; + } + + /** + * Gets the embedding cache storage mode. + * + * @return the embedding cache storage mode ({@code "persistent"} or + * {@code "in-memory"}), or {@code null} to use the default behavior + */ + public String getEmbeddingCacheStorage() { + return embeddingCacheStorage; + } + + /** + * Sets the embedding cache storage mode. + * + * @param embeddingCacheStorage + * {@code "persistent"} to persist embeddings across sessions, or + * {@code "in-memory"} for session-scoped storage + * @return this config for method chaining + */ + public ResumeSessionConfig setEmbeddingCacheStorage(String embeddingCacheStorage) { + this.embeddingCacheStorage = embeddingCacheStorage; + return this; + } + + /** + * Clears the embeddingCacheStorage setting, reverting to the default behavior. + * + * @return this instance for method chaining + */ + public ResumeSessionConfig clearEmbeddingCacheStorage() { + this.embeddingCacheStorage = null; + return this; + } + /** * Gets whether sub-agent streaming events are included. * * @return {@code true} to include sub-agent streaming events, {@code false} to - * suppress them, or {@code null} to use the runtime default + * suppress them, or {@code null} to use the default behavior */ @JsonIgnore public Optional getIncludeSubAgentStreamingEvents() { @@ -1238,6 +1511,14 @@ public ResumeSessionConfig clone() { copy.workingDirectory = this.workingDirectory; copy.configDirectory = this.configDirectory; copy.enableConfigDiscovery = this.enableConfigDiscovery; + copy.skipEmbeddingRetrieval = this.skipEmbeddingRetrieval; + copy.organizationCustomInstructions = this.organizationCustomInstructions; + copy.enableOnDemandInstructionDiscovery = this.enableOnDemandInstructionDiscovery; + copy.enableFileHooks = this.enableFileHooks; + copy.enableHostGitOperations = this.enableHostGitOperations; + copy.enableSessionStore = this.enableSessionStore; + copy.enableSkills = this.enableSkills; + copy.embeddingCacheStorage = this.embeddingCacheStorage; copy.disableResume = this.disableResume; copy.streaming = this.streaming; copy.includeSubAgentStreamingEvents = this.includeSubAgentStreamingEvents; diff --git a/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java b/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java index 5305639ed..66cee6b95 100644 --- a/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java +++ b/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java @@ -80,6 +80,38 @@ public final class ResumeSessionRequest { @JsonProperty("enableConfigDiscovery") private Boolean enableConfigDiscovery; + @JsonProperty("skipEmbeddingRetrieval") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean skipEmbeddingRetrieval; + + @JsonProperty("organizationCustomInstructions") + @JsonInclude(JsonInclude.Include.NON_NULL) + private String organizationCustomInstructions; + + @JsonProperty("enableOnDemandInstructionDiscovery") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean enableOnDemandInstructionDiscovery; + + @JsonProperty("enableFileHooks") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean enableFileHooks; + + @JsonProperty("enableHostGitOperations") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean enableHostGitOperations; + + @JsonProperty("enableSessionStore") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean enableSessionStore; + + @JsonProperty("enableSkills") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean enableSkills; + + @JsonProperty("embeddingCacheStorage") + @JsonInclude(JsonInclude.Include.NON_NULL) + private String embeddingCacheStorage; + @JsonProperty("disableResume") private Boolean disableResume; @@ -377,6 +409,141 @@ public void clearEnableConfigDiscovery() { this.enableConfigDiscovery = null; } + /** Gets skip embedding retrieval flag. @return the flag */ + public Boolean getSkipEmbeddingRetrieval() { + return skipEmbeddingRetrieval; + } + + /** + * Sets skip embedding retrieval flag. @param skipEmbeddingRetrieval the flag + */ + public void setSkipEmbeddingRetrieval(boolean skipEmbeddingRetrieval) { + this.skipEmbeddingRetrieval = skipEmbeddingRetrieval; + } + + /** + * Clears the skipEmbeddingRetrieval setting, reverting to the default behavior. + */ + public void clearSkipEmbeddingRetrieval() { + this.skipEmbeddingRetrieval = null; + } + + /** Gets organization custom instructions. @return the instructions */ + public String getOrganizationCustomInstructions() { + return organizationCustomInstructions; + } + + /** + * Sets organization custom instructions. @param organizationCustomInstructions + * the instructions + */ + public void setOrganizationCustomInstructions(String organizationCustomInstructions) { + this.organizationCustomInstructions = organizationCustomInstructions; + } + + /** Gets enable on-demand instruction discovery flag. @return the flag */ + public Boolean getEnableOnDemandInstructionDiscovery() { + return enableOnDemandInstructionDiscovery; + } + + /** + * Sets enable on-demand instruction discovery flag. @param + * enableOnDemandInstructionDiscovery the flag + */ + public void setEnableOnDemandInstructionDiscovery(boolean enableOnDemandInstructionDiscovery) { + this.enableOnDemandInstructionDiscovery = enableOnDemandInstructionDiscovery; + } + + /** + * Clears the enableOnDemandInstructionDiscovery setting, reverting to the + * default behavior. + */ + public void clearEnableOnDemandInstructionDiscovery() { + this.enableOnDemandInstructionDiscovery = null; + } + + /** Gets enable file hooks flag. @return the flag */ + public Boolean getEnableFileHooks() { + return enableFileHooks; + } + + /** Sets enable file hooks flag. @param enableFileHooks the flag */ + public void setEnableFileHooks(boolean enableFileHooks) { + this.enableFileHooks = enableFileHooks; + } + + /** Clears the enableFileHooks setting, reverting to the default behavior. */ + public void clearEnableFileHooks() { + this.enableFileHooks = null; + } + + /** Gets enable host git operations flag. @return the flag */ + public Boolean getEnableHostGitOperations() { + return enableHostGitOperations; + } + + /** + * Sets enable host git operations flag. @param enableHostGitOperations the flag + */ + public void setEnableHostGitOperations(boolean enableHostGitOperations) { + this.enableHostGitOperations = enableHostGitOperations; + } + + /** + * Clears the enableHostGitOperations setting, reverting to the default + * behavior. + */ + public void clearEnableHostGitOperations() { + this.enableHostGitOperations = null; + } + + /** Gets enable session store flag. @return the flag */ + public Boolean getEnableSessionStore() { + return enableSessionStore; + } + + /** Sets enable session store flag. @param enableSessionStore the flag */ + public void setEnableSessionStore(boolean enableSessionStore) { + this.enableSessionStore = enableSessionStore; + } + + /** Clears the enableSessionStore setting, reverting to the default behavior. */ + public void clearEnableSessionStore() { + this.enableSessionStore = null; + } + + /** Gets enable skills flag. @return the flag */ + public Boolean getEnableSkills() { + return enableSkills; + } + + /** Sets enable skills flag. @param enableSkills the flag */ + public void setEnableSkills(boolean enableSkills) { + this.enableSkills = enableSkills; + } + + /** Clears the enableSkills setting, reverting to the default behavior. */ + public void clearEnableSkills() { + this.enableSkills = null; + } + + /** Gets embedding cache storage mode. @return the mode */ + public String getEmbeddingCacheStorage() { + return embeddingCacheStorage; + } + + /** Sets embedding cache storage mode. @param embeddingCacheStorage the mode */ + public void setEmbeddingCacheStorage(String embeddingCacheStorage) { + this.embeddingCacheStorage = embeddingCacheStorage; + } + + /** + * Clears the embeddingCacheStorage setting, reverting to the default behavior. + */ + public void clearEmbeddingCacheStorage() { + this.embeddingCacheStorage = null; + } + /** Gets disable resume flag. @return the flag */ public Boolean getDisableResume() { return disableResume; diff --git a/java/src/main/java/com/github/copilot/rpc/SessionConfig.java b/java/src/main/java/com/github/copilot/rpc/SessionConfig.java index c84530687..0d83fe799 100644 --- a/java/src/main/java/com/github/copilot/rpc/SessionConfig.java +++ b/java/src/main/java/com/github/copilot/rpc/SessionConfig.java @@ -72,6 +72,14 @@ public class SessionConfig { private List disabledSkills; private String configDirectory; private Boolean enableConfigDiscovery; + private Boolean skipEmbeddingRetrieval; + private String organizationCustomInstructions; + private Boolean enableOnDemandInstructionDiscovery; + private Boolean enableFileHooks; + private Boolean enableHostGitOperations; + private Boolean enableSessionStore; + private Boolean enableSkills; + private String embeddingCacheStorage; private ModelCapabilitiesOverride modelCapabilities; private Consumer onEvent; private List commands; @@ -950,7 +958,7 @@ public SessionConfig setConfigDirectory(String configDirectory) { * * @return an {@link java.util.Optional} containing {@code true} to enable * discovery or {@code false} to disable, or - * {@link java.util.Optional#empty()} to use the runtime default + * {@link java.util.Optional#empty()} to use the default behavior */ @JsonIgnore public Optional getEnableConfigDiscovery() { @@ -986,6 +994,271 @@ public SessionConfig clearEnableConfigDiscovery() { return this; } + /** + * Gets whether embedding-based retrieval is skipped. + * + * @return an {@link java.util.Optional} containing {@code true} to skip + * embedding retrieval or {@code false} to force it, or + * {@link java.util.Optional#empty()} to use the default behavior + */ + @JsonIgnore + public Optional getSkipEmbeddingRetrieval() { + return Optional.ofNullable(skipEmbeddingRetrieval); + } + + /** + * Sets whether to skip embedding-based retrieval. + * + * @param skipEmbeddingRetrieval + * {@code true} to skip embedding retrieval, {@code false} to keep it + * enabled + * @return this config instance for method chaining + */ + public SessionConfig setSkipEmbeddingRetrieval(boolean skipEmbeddingRetrieval) { + this.skipEmbeddingRetrieval = skipEmbeddingRetrieval; + return this; + } + + /** + * Clears the skipEmbeddingRetrieval setting, reverting to the default behavior. + * + * @return this instance for method chaining + */ + public SessionConfig clearSkipEmbeddingRetrieval() { + this.skipEmbeddingRetrieval = null; + return this; + } + + /** + * Gets the organization-level custom instructions. + * + * @return the organization-level custom instructions, or {@code null} if not + * set + */ + public String getOrganizationCustomInstructions() { + return organizationCustomInstructions; + } + + /** + * Sets organization-level custom instructions. + * + * @param organizationCustomInstructions + * the organization-level custom instructions + * @return this config instance for method chaining + */ + public SessionConfig setOrganizationCustomInstructions(String organizationCustomInstructions) { + this.organizationCustomInstructions = organizationCustomInstructions; + return this; + } + + /** + * Gets whether on-demand instruction file discovery is enabled. + * + * @return an {@link java.util.Optional} containing {@code true} to enable + * on-demand discovery or {@code false} to disable it, or + * {@link java.util.Optional#empty()} to use the default behavior + */ + @JsonIgnore + public Optional getEnableOnDemandInstructionDiscovery() { + return Optional.ofNullable(enableOnDemandInstructionDiscovery); + } + + /** + * Sets whether instruction files are discovered on demand. + * + * @param enableOnDemandInstructionDiscovery + * {@code true} to enable on-demand instruction discovery, + * {@code false} to disable it + * @return this config instance for method chaining + */ + public SessionConfig setEnableOnDemandInstructionDiscovery(boolean enableOnDemandInstructionDiscovery) { + this.enableOnDemandInstructionDiscovery = enableOnDemandInstructionDiscovery; + return this; + } + + /** + * Clears the enableOnDemandInstructionDiscovery setting, reverting to the + * default behavior. + * + * @return this instance for method chaining + */ + public SessionConfig clearEnableOnDemandInstructionDiscovery() { + this.enableOnDemandInstructionDiscovery = null; + return this; + } + + /** + * Gets whether file-based hooks are enabled. + * + * @return an {@link java.util.Optional} containing {@code true} to enable file + * hooks or {@code false} to disable them, or + * {@link java.util.Optional#empty()} to use the default behavior + */ + @JsonIgnore + public Optional getEnableFileHooks() { + return Optional.ofNullable(enableFileHooks); + } + + /** + * Sets whether file-based hooks from {@code .github/hooks/} are enabled. + * + * @param enableFileHooks + * {@code true} to enable file hooks, {@code false} to disable them + * @return this config instance for method chaining + */ + public SessionConfig setEnableFileHooks(boolean enableFileHooks) { + this.enableFileHooks = enableFileHooks; + return this; + } + + /** + * Clears the enableFileHooks setting, reverting to the default behavior. + * + * @return this instance for method chaining + */ + public SessionConfig clearEnableFileHooks() { + this.enableFileHooks = null; + return this; + } + + /** + * Gets whether host git operations are enabled. + * + * @return an {@link java.util.Optional} containing {@code true} to enable host + * git operations or {@code false} to disable them, or + * {@link java.util.Optional#empty()} to use the default behavior + */ + @JsonIgnore + public Optional getEnableHostGitOperations() { + return Optional.ofNullable(enableHostGitOperations); + } + + /** + * Sets whether git operations on the host filesystem are enabled. + * + * @param enableHostGitOperations + * {@code true} to enable host git operations, {@code false} to + * disable them + * @return this config instance for method chaining + */ + public SessionConfig setEnableHostGitOperations(boolean enableHostGitOperations) { + this.enableHostGitOperations = enableHostGitOperations; + return this; + } + + /** + * Clears the enableHostGitOperations setting, reverting to the default + * behavior. + * + * @return this instance for method chaining + */ + public SessionConfig clearEnableHostGitOperations() { + this.enableHostGitOperations = null; + return this; + } + + /** + * Gets whether the cross-session store is enabled. + * + * @return an {@link java.util.Optional} containing {@code true} to enable the + * session store or {@code false} to disable it, or + * {@link java.util.Optional#empty()} to use the default behavior + */ + @JsonIgnore + public Optional getEnableSessionStore() { + return Optional.ofNullable(enableSessionStore); + } + + /** + * Sets whether the cross-session store is enabled. + * + * @param enableSessionStore + * {@code true} to enable the session store, {@code false} to disable + * it + * @return this config instance for method chaining + */ + public SessionConfig setEnableSessionStore(boolean enableSessionStore) { + this.enableSessionStore = enableSessionStore; + return this; + } + + /** + * Clears the enableSessionStore setting, reverting to the default behavior. + * + * @return this instance for method chaining + */ + public SessionConfig clearEnableSessionStore() { + this.enableSessionStore = null; + return this; + } + + /** + * Gets whether skill loading is enabled. + * + * @return an {@link java.util.Optional} containing {@code true} to enable skill + * loading or {@code false} to disable it, or + * {@link java.util.Optional#empty()} to use the default behavior + */ + @JsonIgnore + public Optional getEnableSkills() { + return Optional.ofNullable(enableSkills); + } + + /** + * Sets whether skill loading is enabled. + * + * @param enableSkills + * {@code true} to enable skill loading, {@code false} to disable it + * @return this config instance for method chaining + */ + public SessionConfig setEnableSkills(boolean enableSkills) { + this.enableSkills = enableSkills; + return this; + } + + /** + * Clears the enableSkills setting, reverting to the default behavior. + * + * @return this instance for method chaining + */ + public SessionConfig clearEnableSkills() { + this.enableSkills = null; + return this; + } + + /** + * Gets the embedding cache storage mode. + * + * @return the embedding cache storage mode ({@code "persistent"} or + * {@code "in-memory"}), or {@code null} to use the default behavior + */ + public String getEmbeddingCacheStorage() { + return embeddingCacheStorage; + } + + /** + * Sets the embedding cache storage mode. + * + * @param embeddingCacheStorage + * {@code "persistent"} to persist embeddings across sessions, or + * {@code "in-memory"} for session-scoped storage + * @return this config instance for method chaining + */ + public SessionConfig setEmbeddingCacheStorage(String embeddingCacheStorage) { + this.embeddingCacheStorage = embeddingCacheStorage; + return this; + } + + /** + * Clears the embeddingCacheStorage setting, reverting to the default behavior. + * + * @return this instance for method chaining + */ + public SessionConfig clearEmbeddingCacheStorage() { + this.embeddingCacheStorage = null; + return this; + } + /** * Gets whether sub-agent streaming events are included. * @@ -1375,6 +1648,14 @@ public SessionConfig clone() { copy.disabledSkills = this.disabledSkills != null ? new ArrayList<>(this.disabledSkills) : null; copy.configDirectory = this.configDirectory; copy.enableConfigDiscovery = this.enableConfigDiscovery; + copy.skipEmbeddingRetrieval = this.skipEmbeddingRetrieval; + copy.organizationCustomInstructions = this.organizationCustomInstructions; + copy.enableOnDemandInstructionDiscovery = this.enableOnDemandInstructionDiscovery; + copy.enableFileHooks = this.enableFileHooks; + copy.enableHostGitOperations = this.enableHostGitOperations; + copy.enableSessionStore = this.enableSessionStore; + copy.enableSkills = this.enableSkills; + copy.embeddingCacheStorage = this.embeddingCacheStorage; copy.modelCapabilities = this.modelCapabilities; copy.onEvent = this.onEvent; copy.commands = this.commands != null ? new ArrayList<>(this.commands) : null; diff --git a/java/src/test/java/com/github/copilot/ConfigCloneTest.java b/java/src/test/java/com/github/copilot/ConfigCloneTest.java index e40a3048b..2efafdfc5 100644 --- a/java/src/test/java/com/github/copilot/ConfigCloneTest.java +++ b/java/src/test/java/com/github/copilot/ConfigCloneTest.java @@ -230,6 +230,25 @@ void sessionConfigEnableSessionTelemetryDefaultIsNull() { assertTrue(cloned.getEnableSessionTelemetry().isEmpty()); } + @Test + void sessionConfigGranularMultitenancyFieldsCopied() { + SessionConfig original = new SessionConfig().setSkipEmbeddingRetrieval(true) + .setOrganizationCustomInstructions("Org instructions").setEnableOnDemandInstructionDiscovery(false) + .setEmbeddingCacheStorage("persistent").setEnableFileHooks(true).setEnableHostGitOperations(false) + .setEnableSessionStore(true).setEnableSkills(false); + + SessionConfig cloned = original.clone(); + + assertTrue(cloned.getSkipEmbeddingRetrieval().orElse(false)); + assertEquals("Org instructions", cloned.getOrganizationCustomInstructions()); + assertFalse(cloned.getEnableOnDemandInstructionDiscovery().orElse(true)); + assertEquals("persistent", cloned.getEmbeddingCacheStorage()); + assertTrue(cloned.getEnableFileHooks().orElse(false)); + assertFalse(cloned.getEnableHostGitOperations().orElse(true)); + assertTrue(cloned.getEnableSessionStore().orElse(false)); + assertFalse(cloned.getEnableSkills().orElse(true)); + } + @Test void resumeSessionConfigEnableSessionTelemetryCopied() { ResumeSessionConfig original = new ResumeSessionConfig(); @@ -249,6 +268,26 @@ void resumeSessionConfigEnableSessionTelemetryDefaultIsNull() { assertTrue(cloned.getEnableSessionTelemetry().isEmpty()); } + @Test + void resumeSessionConfigGranularMultitenancyFieldsCopied() { + ResumeSessionConfig original = new ResumeSessionConfig().setSkipEmbeddingRetrieval(false) + .setOrganizationCustomInstructions("Resume org instructions") + .setEnableOnDemandInstructionDiscovery(true).setEmbeddingCacheStorage("persistent") + .setEnableFileHooks(false).setEnableHostGitOperations(true).setEnableSessionStore(false) + .setEnableSkills(true); + + ResumeSessionConfig cloned = original.clone(); + + assertFalse(cloned.getSkipEmbeddingRetrieval().orElse(true)); + assertEquals("Resume org instructions", cloned.getOrganizationCustomInstructions()); + assertTrue(cloned.getEnableOnDemandInstructionDiscovery().orElse(false)); + assertEquals("persistent", cloned.getEmbeddingCacheStorage()); + assertFalse(cloned.getEnableFileHooks().orElse(true)); + assertTrue(cloned.getEnableHostGitOperations().orElse(false)); + assertFalse(cloned.getEnableSessionStore().orElse(true)); + assertTrue(cloned.getEnableSkills().orElse(false)); + } + @Test void clonePreservesNullFields() { CopilotClientOptions opts = new CopilotClientOptions(); diff --git a/java/src/test/java/com/github/copilot/OptionalApiAndJacksonTest.java b/java/src/test/java/com/github/copilot/OptionalApiAndJacksonTest.java index 1db593dbe..2b6a79563 100644 --- a/java/src/test/java/com/github/copilot/OptionalApiAndJacksonTest.java +++ b/java/src/test/java/com/github/copilot/OptionalApiAndJacksonTest.java @@ -353,6 +353,37 @@ void sessionConfig_includeSubAgentStreamingEventsValue() { assertTrue(cfg.getIncludeSubAgentStreamingEvents().get()); } + @Test + void sessionConfig_granularMultitenancyFieldsValue() { + var cfg = new SessionConfig(); + assertTrue(cfg.getSkipEmbeddingRetrieval().isEmpty()); + assertNull(cfg.getOrganizationCustomInstructions()); + assertTrue(cfg.getEnableOnDemandInstructionDiscovery().isEmpty()); + assertNull(cfg.getEmbeddingCacheStorage()); + assertTrue(cfg.getEnableFileHooks().isEmpty()); + assertTrue(cfg.getEnableHostGitOperations().isEmpty()); + assertTrue(cfg.getEnableSessionStore().isEmpty()); + assertTrue(cfg.getEnableSkills().isEmpty()); + + cfg.setSkipEmbeddingRetrieval(true); + cfg.setOrganizationCustomInstructions("Org instructions"); + cfg.setEnableOnDemandInstructionDiscovery(false); + cfg.setEmbeddingCacheStorage("persistent"); + cfg.setEnableFileHooks(true); + cfg.setEnableHostGitOperations(false); + cfg.setEnableSessionStore(true); + cfg.setEnableSkills(false); + + assertTrue(cfg.getSkipEmbeddingRetrieval().get()); + assertEquals("Org instructions", cfg.getOrganizationCustomInstructions()); + assertFalse(cfg.getEnableOnDemandInstructionDiscovery().get()); + assertEquals("persistent", cfg.getEmbeddingCacheStorage()); + assertTrue(cfg.getEnableFileHooks().get()); + assertFalse(cfg.getEnableHostGitOperations().get()); + assertTrue(cfg.getEnableSessionStore().get()); + assertFalse(cfg.getEnableSkills().get()); + } + @Test void resumeSessionConfig_enableSessionTelemetryValue() { var cfg = new ResumeSessionConfig(); @@ -383,6 +414,37 @@ void resumeSessionConfig_includeSubAgentStreamingEventsValue() { assertFalse(cfg.getIncludeSubAgentStreamingEvents().get()); } + @Test + void resumeSessionConfig_granularMultitenancyFieldsValue() { + var cfg = new ResumeSessionConfig(); + assertTrue(cfg.getSkipEmbeddingRetrieval().isEmpty()); + assertNull(cfg.getOrganizationCustomInstructions()); + assertTrue(cfg.getEnableOnDemandInstructionDiscovery().isEmpty()); + assertNull(cfg.getEmbeddingCacheStorage()); + assertTrue(cfg.getEnableFileHooks().isEmpty()); + assertTrue(cfg.getEnableHostGitOperations().isEmpty()); + assertTrue(cfg.getEnableSessionStore().isEmpty()); + assertTrue(cfg.getEnableSkills().isEmpty()); + + cfg.setSkipEmbeddingRetrieval(false); + cfg.setOrganizationCustomInstructions("Resume org instructions"); + cfg.setEnableOnDemandInstructionDiscovery(true); + cfg.setEmbeddingCacheStorage("persistent"); + cfg.setEnableFileHooks(false); + cfg.setEnableHostGitOperations(true); + cfg.setEnableSessionStore(false); + cfg.setEnableSkills(true); + + assertFalse(cfg.getSkipEmbeddingRetrieval().get()); + assertEquals("Resume org instructions", cfg.getOrganizationCustomInstructions()); + assertTrue(cfg.getEnableOnDemandInstructionDiscovery().get()); + assertEquals("persistent", cfg.getEmbeddingCacheStorage()); + assertFalse(cfg.getEnableFileHooks().get()); + assertTrue(cfg.getEnableHostGitOperations().get()); + assertFalse(cfg.getEnableSessionStore().get()); + assertTrue(cfg.getEnableSkills().get()); + } + @Test void infiniteSessionConfig_thresholdValues() { var cfg = new InfiniteSessionConfig(); @@ -565,11 +627,38 @@ void jackson_deserializeInfiniteSessionConfigEmpty() throws Exception { // access: Jackson writes the field when set and omits it when cleared. // // Classes without @JsonProperty on fields (SessionConfig, - // CopilotClientOptions, TelemetryConfig, ProviderConfig) are not - // directly serialized — their values are copied to wire DTOs by - // SessionRequestBuilder. The @JsonIgnore on their Optional-returning - // getters prevents Jackson from attempting to serialize Optional - // wrappers if the class is ever processed. + // CopilotClientOptions, TelemetryConfig, ProviderConfig) are normally + // copied to wire DTOs by SessionRequestBuilder. Their scalar getters can + // still be serialized directly, while @JsonIgnore on Optional-returning + // getters prevents Jackson from attempting to serialize Optional wrappers. + + @Test + void jackson_sessionConfigEmbeddingCacheStorageSerialized() throws Exception { + var cfg = new SessionConfig(); + cfg.setEmbeddingCacheStorage("persistent"); + + String withField = MAPPER.writeValueAsString(cfg); + assertTrue(withField.contains("\"embeddingCacheStorage\":\"persistent\"")); + + cfg.clearEmbeddingCacheStorage(); + + String cleared = MAPPER.writeValueAsString(cfg); + assertFalse(cleared.contains("embeddingCacheStorage")); + } + + @Test + void jackson_resumeSessionConfigEmbeddingCacheStorageSerialized() throws Exception { + var cfg = new ResumeSessionConfig(); + cfg.setEmbeddingCacheStorage("persistent"); + + String withField = MAPPER.writeValueAsString(cfg); + assertTrue(withField.contains("\"embeddingCacheStorage\":\"persistent\"")); + + cfg.clearEmbeddingCacheStorage(); + + String cleared = MAPPER.writeValueAsString(cfg); + assertFalse(cleared.contains("embeddingCacheStorage")); + } @Test void jackson_infiniteSessionConfigClearedFieldsOmitted() throws Exception { diff --git a/java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java b/java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java index 9fca584d1..c97114b27 100644 --- a/java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java +++ b/java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java @@ -250,30 +250,66 @@ void testBuildResumeRequestSetsClientName() { assertEquals("my-app", request.getClientName()); } + @Test + void testBuildCreateRequestPropagatesGranularMultitenancyFields() { + var config = new SessionConfig().setSkipEmbeddingRetrieval(true) + .setOrganizationCustomInstructions("Create org instructions") + .setEnableOnDemandInstructionDiscovery(false).setEnableFileHooks(true).setEnableHostGitOperations(false) + .setEnableSessionStore(true).setEnableSkills(false); + + CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(config); + + assertTrue(request.getSkipEmbeddingRetrieval()); + assertEquals("Create org instructions", request.getOrganizationCustomInstructions()); + assertFalse(request.getEnableOnDemandInstructionDiscovery()); + assertTrue(request.getEnableFileHooks()); + assertFalse(request.getEnableHostGitOperations()); + assertTrue(request.getEnableSessionStore()); + assertFalse(request.getEnableSkills()); + } + + @Test + void testBuildResumeRequestPropagatesGranularMultitenancyFields() { + var config = new ResumeSessionConfig().setSkipEmbeddingRetrieval(false) + .setOrganizationCustomInstructions("Resume org instructions") + .setEnableOnDemandInstructionDiscovery(true).setEnableFileHooks(false).setEnableHostGitOperations(true) + .setEnableSessionStore(false).setEnableSkills(true); + + ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-11", config); + + assertFalse(request.getSkipEmbeddingRetrieval()); + assertEquals("Resume org instructions", request.getOrganizationCustomInstructions()); + assertTrue(request.getEnableOnDemandInstructionDiscovery()); + assertFalse(request.getEnableFileHooks()); + assertTrue(request.getEnableHostGitOperations()); + assertFalse(request.getEnableSessionStore()); + assertTrue(request.getEnableSkills()); + } + @Test void testBuildResumeRequestPassesThroughNullMcpOAuthTokenStorage() { var config = new ResumeSessionConfig(); - ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-11", config); + ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-12", config); assertNull(request.getMcpOAuthTokenStorage()); } @Test void testBuildResumeRequestForwardsExplicitMcpOAuthTokenStorage() { var config = new ResumeSessionConfig().setMcpOAuthTokenStorage("persistent"); - ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-12", config); + ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-13", config); assertEquals("persistent", request.getMcpOAuthTokenStorage()); } @Test void testBuildResumeRequestNullConfigHasNullMcpOAuthTokenStorage() { - ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-13", null); + ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-14", null); assertNull(request.getMcpOAuthTokenStorage()); } @Test void testBuildResumeRequestSetsReasoningSummary() { var config = new ResumeSessionConfig().setReasoningSummary("none"); - ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-14", config); + ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-15", config); assertEquals("none", request.getReasoningSummary()); } @@ -282,7 +318,7 @@ void testBuildResumeRequestSetsPluginDirectoriesAndLargeOutput() { var largeOutput = new LargeToolOutputConfig().setEnabled(false).setMaxSizeBytes(2048L) .setOutputDirectory("/tmp/resume"); var config = new ResumeSessionConfig().setPluginDirectories(List.of("/plugins/r")).setLargeOutput(largeOutput); - ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-15", config); + ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-16", config); assertEquals(List.of("/plugins/r"), request.getPluginDirectories()); assertEquals(largeOutput, request.getLargeOutput()); } diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index 26a5dc1dc..5ee36b78b 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -908,7 +908,17 @@ export class CopilotClient { /** Mode-specific defaults spread under the caller's config (app values win). */ private configDefaultsForMode(): Partial { if (this.options.mode === "empty") { - return { enableSessionTelemetry: false, mcpOAuthTokenStorage: "in-memory" }; + return { + enableSessionTelemetry: false, + mcpOAuthTokenStorage: "in-memory", + skipEmbeddingRetrieval: true, + embeddingCacheStorage: "in-memory", + enableOnDemandInstructionDiscovery: false, + enableFileHooks: false, + enableHostGitOperations: false, + enableSessionStore: false, + enableSkills: false, + }; } return {}; } @@ -1131,6 +1141,14 @@ export class CopilotClient { agent: config.agent, configDir: config.configDirectory, enableConfigDiscovery: config.enableConfigDiscovery, + skipEmbeddingRetrieval: config.skipEmbeddingRetrieval, + embeddingCacheStorage: config.embeddingCacheStorage, + organizationCustomInstructions: config.organizationCustomInstructions, + enableOnDemandInstructionDiscovery: config.enableOnDemandInstructionDiscovery, + enableFileHooks: config.enableFileHooks, + enableHostGitOperations: config.enableHostGitOperations, + enableSessionStore: config.enableSessionStore, + enableSkills: config.enableSkills, skillDirectories: config.skillDirectories, pluginDirectories: config.pluginDirectories, instructionDirectories: config.instructionDirectories, @@ -1295,6 +1313,14 @@ export class CopilotClient { workingDirectory: config.workingDirectory, configDir: config.configDirectory, enableConfigDiscovery: config.enableConfigDiscovery, + skipEmbeddingRetrieval: config.skipEmbeddingRetrieval, + embeddingCacheStorage: config.embeddingCacheStorage, + organizationCustomInstructions: config.organizationCustomInstructions, + enableOnDemandInstructionDiscovery: config.enableOnDemandInstructionDiscovery, + enableFileHooks: config.enableFileHooks, + enableHostGitOperations: config.enableHostGitOperations, + enableSessionStore: config.enableSessionStore, + enableSkills: config.enableSkills, streaming: config.streaming, includeSubAgentStreamingEvents: config.includeSubAgentStreamingEvents ?? true, mcpServers: toWireMcpServers(config.mcpServers), diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index dd5604af0..b6ce9b774 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -1914,6 +1914,61 @@ export interface SessionConfigBase { */ gitHubToken?: string; + /** + * When true, skips embedding-based retrieval for this session. + * Use in multitenant deployments to prevent cross-session information leakage + * through the shared embedding cache. + */ + skipEmbeddingRetrieval?: boolean; + + /** + * Controls how the embedding cache is stored for this session. + * - `"persistent"`: Embeddings are cached on disk and shared across sessions/restarts. + * - `"in-memory"`: Embeddings are cached in memory only and discarded when the session ends. + */ + embeddingCacheStorage?: "persistent" | "in-memory"; + + /** + * Organization-level custom instructions to include in the system prompt. + * Allows hosts to inject organization-specific guidance without relying on + * filesystem-based instruction discovery. + */ + organizationCustomInstructions?: string; + + /** + * When true, enables on-demand discovery of instruction files (AGENTS.md, + * .github/copilot-instructions.md, etc.) after successful file views. + */ + enableOnDemandInstructionDiscovery?: boolean; + + /** + * When true, enables loading of file-based hooks from `.github/hooks/`. + * This is separate from the `hooks` callback parameter which gates SDK + * hook event registration. + */ + enableFileHooks?: boolean; + + /** + * When true, enables git operations on the host filesystem (branch detection, + * file status, commit history). When false, no git context is surfaced in + * the system prompt. + */ + enableHostGitOperations?: boolean; + + /** + * When true, enables the cross-session store for search and retrieval + * across sessions. When false, session content is not written to or + * read from the shared session store. + */ + enableSessionStore?: boolean; + + /** + * When true, enables skill loading (including builtin skills and discovered + * skill directories). When false, no skills are loaded regardless of + * `skillDirectories` or `enableConfigDiscovery` settings. + */ + enableSkills?: boolean; + /** * Per-session remote behavior control: * - `"off"` — local only, no remote export (default) diff --git a/nodejs/test/toolSet.test.ts b/nodejs/test/toolSet.test.ts index 4da97aa94..b77b79707 100644 --- a/nodejs/test/toolSet.test.ts +++ b/nodejs/test/toolSet.test.ts @@ -461,6 +461,89 @@ describe("Empty-mode safe defaults", () => { }); }); + it("applies restrictive defaults for granular multitenancy flags in empty mode", async () => { + const { client, spy } = await setupClient(); + await client.createSession({ + onPermissionRequest: approveAll, + availableTools: new ToolSet().addBuiltIn(BuiltInTools.Isolated), + }); + const payload = createPayload(spy); + expect(payload.skipEmbeddingRetrieval).toBe(true); + expect(payload.embeddingCacheStorage).toBe("in-memory"); + expect(payload.enableOnDemandInstructionDiscovery).toBe(false); + expect(payload.enableFileHooks).toBe(false); + expect(payload.enableHostGitOperations).toBe(false); + expect(payload.enableSessionStore).toBe(false); + expect(payload.enableSkills).toBe(false); + }); + + it("respects app-supplied overrides for granular multitenancy flags in empty mode", async () => { + const { client, spy } = await setupClient(); + await client.createSession({ + onPermissionRequest: approveAll, + availableTools: new ToolSet().addBuiltIn(BuiltInTools.Isolated), + skipEmbeddingRetrieval: false, + enableOnDemandInstructionDiscovery: true, + enableFileHooks: true, + enableHostGitOperations: true, + enableSessionStore: true, + enableSkills: true, + }); + const payload = createPayload(spy); + expect(payload.skipEmbeddingRetrieval).toBe(false); + expect(payload.enableOnDemandInstructionDiscovery).toBe(true); + expect(payload.enableFileHooks).toBe(true); + expect(payload.enableHostGitOperations).toBe(true); + expect(payload.enableSessionStore).toBe(true); + expect(payload.enableSkills).toBe(true); + }); + + it("passes organizationCustomInstructions through on create", async () => { + const { client, spy } = await setupClient(); + await client.createSession({ + onPermissionRequest: approveAll, + availableTools: new ToolSet().addBuiltIn(BuiltInTools.Isolated), + organizationCustomInstructions: "Follow org coding standards", + }); + const payload = createPayload(spy); + expect(payload.organizationCustomInstructions).toBe("Follow org coding standards"); + }); + + it("does NOT apply granular multitenancy flag defaults in copilot-cli mode", async () => { + const { client, spy } = await setupClient("copilot-cli"); + await client.createSession({ + onPermissionRequest: approveAll, + availableTools: ["builtin:bash"], + }); + const payload = createPayload(spy); + expect(payload.skipEmbeddingRetrieval).toBeUndefined(); + expect(payload.enableOnDemandInstructionDiscovery).toBeUndefined(); + expect(payload.enableFileHooks).toBeUndefined(); + expect(payload.enableHostGitOperations).toBeUndefined(); + expect(payload.enableSessionStore).toBeUndefined(); + expect(payload.enableSkills).toBeUndefined(); + }); + + it("applies granular multitenancy flag defaults on session.resume in empty mode", async () => { + const { client, spy } = await setupClient(); + const session = await client.createSession({ + onPermissionRequest: approveAll, + availableTools: new ToolSet().addBuiltIn(BuiltInTools.Isolated), + }); + spy.mockClear(); + await client.resumeSession(session.sessionId, { + onPermissionRequest: approveAll, + availableTools: new ToolSet().addBuiltIn(BuiltInTools.Isolated), + }); + const resumePayload = spy.mock.calls.find(([m]) => m === "session.resume")![1] as any; + expect(resumePayload.skipEmbeddingRetrieval).toBe(true); + expect(resumePayload.enableOnDemandInstructionDiscovery).toBe(false); + expect(resumePayload.enableFileHooks).toBe(false); + expect(resumePayload.enableHostGitOperations).toBe(false); + expect(resumePayload.enableSessionStore).toBe(false); + expect(resumePayload.enableSkills).toBe(false); + }); + it("forwards the four flags in copilot-cli mode when the app sets them", async () => { const { client, spy } = await setupClient("copilot-cli"); await client.createSession({ diff --git a/python/copilot/_mode.py b/python/copilot/_mode.py index 928eefbec..9323423f6 100644 --- a/python/copilot/_mode.py +++ b/python/copilot/_mode.py @@ -172,16 +172,82 @@ def _system_message_for_mode( return out +def _empty_mode_bool_default( + mode: CopilotClientMode | None, + supplied: bool | None, + empty_default: bool, +) -> bool | None: + if mode == "empty" and supplied is None: + return empty_default + return supplied + + def _enable_session_telemetry_default( mode: CopilotClientMode | None, supplied: bool | None, ) -> bool | None: """Empty mode defaults telemetry to False; caller value wins.""" + return _empty_mode_bool_default(mode, supplied, False) + + +def _skip_embedding_retrieval_default( + mode: CopilotClientMode | None, + supplied: bool | None, +) -> bool | None: + """Empty mode defaults embedding retrieval to off; caller value wins.""" + return _empty_mode_bool_default(mode, supplied, True) + + +def _embedding_cache_storage_default( + mode: CopilotClientMode | None, + supplied: Literal["persistent", "in-memory"] | None, +) -> Literal["persistent", "in-memory"] | None: + """Empty mode defaults embedding cache storage to in-memory; caller value wins.""" if mode == "empty" and supplied is None: - return False + return "in-memory" return supplied +def _enable_on_demand_instruction_discovery_default( + mode: CopilotClientMode | None, + supplied: bool | None, +) -> bool | None: + """Empty mode defaults on-demand instruction discovery to False.""" + return _empty_mode_bool_default(mode, supplied, False) + + +def _enable_file_hooks_default( + mode: CopilotClientMode | None, + supplied: bool | None, +) -> bool | None: + """Empty mode defaults file hooks to False; caller value wins.""" + return _empty_mode_bool_default(mode, supplied, False) + + +def _enable_host_git_operations_default( + mode: CopilotClientMode | None, + supplied: bool | None, +) -> bool | None: + """Empty mode defaults host git operations to False; caller value wins.""" + return _empty_mode_bool_default(mode, supplied, False) + + +def _enable_session_store_default( + mode: CopilotClientMode | None, + supplied: bool | None, +) -> bool | None: + """Empty mode defaults the session store to False; caller value wins.""" + return _empty_mode_bool_default(mode, supplied, False) + + +def _enable_skills_default( + mode: CopilotClientMode | None, + supplied: bool | None, +) -> bool | None: + """Empty mode defaults skills to False; caller value wins.""" + return _empty_mode_bool_default(mode, supplied, False) + + def _mcp_oauth_token_storage_default( mode: CopilotClientMode | None, supplied: Literal["persistent", "in-memory"] | None, diff --git a/python/copilot/client.py b/python/copilot/client.py index f22734014..66c56a595 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -37,12 +37,19 @@ from ._mode import ( CopilotClientMode, ToolSet, + _embedding_cache_storage_default, + _enable_file_hooks_default, + _enable_host_git_operations_default, + _enable_on_demand_instruction_discovery_default, + _enable_session_store_default, _enable_session_telemetry_default, + _enable_skills_default, _mcp_oauth_token_storage_default, _normalize_tool_filter, _post_create_options_patch, _require_available_tools_for_empty_mode, _require_storage_for_empty_mode, + _skip_embedding_retrieval_default, _system_message_for_mode, _validate_tool_filter_list, ) @@ -1573,11 +1580,19 @@ async def create_session( include_sub_agent_streaming_events: bool | None = None, mcp_servers: dict[str, MCPServerConfig] | None = None, mcp_oauth_token_storage: Literal["persistent", "in-memory"] | None = None, + embedding_cache_storage: Literal["persistent", "in-memory"] | None = None, custom_agents: list[CustomAgentConfig] | None = None, default_agent: DefaultAgentConfig | dict[str, Any] | None = None, agent: str | None = None, config_directory: str | None = None, enable_config_discovery: bool | None = None, + skip_embedding_retrieval: bool | None = None, + organization_custom_instructions: str | None = None, + enable_on_demand_instruction_discovery: bool | None = None, + enable_file_hooks: bool | None = None, + enable_host_git_operations: bool | None = None, + enable_session_store: bool | None = None, + enable_skills: bool | None = None, skill_directories: list[str] | None = None, plugin_directories: list[str] | None = None, instruction_directories: list[str] | None = None, @@ -1651,6 +1666,10 @@ async def create_session( ``"persistent"`` uses the OS keychain (shared across sessions). ``"in-memory"`` stores tokens in memory (discarded on session end). Defaults to ``"in-memory"`` for safe multitenant behavior. + embedding_cache_storage: Controls how embedding caches are stored. + `"persistent"` uses disk-based storage (shared across sessions). + `"in-memory"` stores embeddings in memory (discarded on session end). + Defaults to `"in-memory"` in empty mode. custom_agents: Custom agent configurations. default_agent: Configuration for the default agent, including tool visibility controls. @@ -1663,6 +1682,14 @@ async def create_session( explicit values taking precedence on name collision. Custom instruction files (``.github/copilot-instructions.md``, ``AGENTS.md``, etc.) are always loaded regardless of this setting. + skip_embedding_retrieval: When True, skips embedding-based retrieval. + organization_custom_instructions: Organization-level custom instructions. + enable_on_demand_instruction_discovery: Enables on-demand instruction file + discovery. + enable_file_hooks: Enables file-based hooks from ``.github/hooks/``. + enable_host_git_operations: Enables git operations on the host filesystem. + enable_session_store: Enables the cross-session store. + enable_skills: Enables skill loading. skill_directories: Directories to search for skills. instruction_directories: Additional directories to search for custom instruction files. @@ -1729,8 +1756,19 @@ async def create_session( _validate_tool_filter_list("excluded_tools", excluded_tools) # Mode "empty" strips environment_context from the system message. system_message = _system_message_for_mode(mode, system_message) - # Mode "empty" defaults telemetry to off; caller wins. + # Mode "empty" defaults selected session config flags to restrictive values; + # caller-supplied values win. enable_session_telemetry = _enable_session_telemetry_default(mode, enable_session_telemetry) + skip_embedding_retrieval = _skip_embedding_retrieval_default(mode, skip_embedding_retrieval) + enable_on_demand_instruction_discovery = _enable_on_demand_instruction_discovery_default( + mode, enable_on_demand_instruction_discovery + ) + enable_file_hooks = _enable_file_hooks_default(mode, enable_file_hooks) + enable_host_git_operations = _enable_host_git_operations_default( + mode, enable_host_git_operations + ) + enable_session_store = _enable_session_store_default(mode, enable_session_store) + enable_skills = _enable_skills_default(mode, enable_skills) payload: dict[str, Any] = {} if model: @@ -1825,6 +1863,9 @@ async def create_session( mcp_oauth_token_storage = _mcp_oauth_token_storage_default(mode, mcp_oauth_token_storage) if mcp_oauth_token_storage is not None: payload["mcpOAuthTokenStorage"] = mcp_oauth_token_storage + embedding_cache_storage = _embedding_cache_storage_default(mode, embedding_cache_storage) + if embedding_cache_storage is not None: + payload["embeddingCacheStorage"] = embedding_cache_storage payload["envValueMode"] = "direct" # Add custom agents configuration if provided @@ -1848,6 +1889,20 @@ async def create_session( # Add config discovery flag if provided if enable_config_discovery is not None: payload["enableConfigDiscovery"] = enable_config_discovery + if skip_embedding_retrieval is not None: + payload["skipEmbeddingRetrieval"] = skip_embedding_retrieval + if organization_custom_instructions is not None: + payload["organizationCustomInstructions"] = organization_custom_instructions + if enable_on_demand_instruction_discovery is not None: + payload["enableOnDemandInstructionDiscovery"] = enable_on_demand_instruction_discovery + if enable_file_hooks is not None: + payload["enableFileHooks"] = enable_file_hooks + if enable_host_git_operations is not None: + payload["enableHostGitOperations"] = enable_host_git_operations + if enable_session_store is not None: + payload["enableSessionStore"] = enable_session_store + if enable_skills is not None: + payload["enableSkills"] = enable_skills # Add skill directories configuration if provided if skill_directories: @@ -2086,11 +2141,19 @@ async def resume_session( include_sub_agent_streaming_events: bool | None = None, mcp_servers: dict[str, MCPServerConfig] | None = None, mcp_oauth_token_storage: Literal["persistent", "in-memory"] | None = None, + embedding_cache_storage: Literal["persistent", "in-memory"] | None = None, custom_agents: list[CustomAgentConfig] | None = None, default_agent: DefaultAgentConfig | dict[str, Any] | None = None, agent: str | None = None, config_directory: str | None = None, enable_config_discovery: bool | None = None, + skip_embedding_retrieval: bool | None = None, + organization_custom_instructions: str | None = None, + enable_on_demand_instruction_discovery: bool | None = None, + enable_file_hooks: bool | None = None, + enable_host_git_operations: bool | None = None, + enable_session_store: bool | None = None, + enable_skills: bool | None = None, skill_directories: list[str] | None = None, plugin_directories: list[str] | None = None, instruction_directories: list[str] | None = None, @@ -2165,6 +2228,10 @@ async def resume_session( ``"persistent"`` uses the OS keychain (shared across sessions). ``"in-memory"`` stores tokens in memory (discarded on session end). Defaults to ``"in-memory"`` for safe multitenant behavior. + embedding_cache_storage: Controls how embedding caches are stored. + `"persistent"` uses disk-based storage (shared across sessions). + `"in-memory"` stores embeddings in memory (discarded on session end). + Defaults to `"in-memory"` in empty mode. custom_agents: Custom agent configurations. default_agent: Configuration for the default agent, including tool visibility controls. @@ -2177,6 +2244,14 @@ async def resume_session( explicit values taking precedence on name collision. Custom instruction files (``.github/copilot-instructions.md``, ``AGENTS.md``, etc.) are always loaded regardless of this setting. + skip_embedding_retrieval: When True, skips embedding-based retrieval. + organization_custom_instructions: Organization-level custom instructions. + enable_on_demand_instruction_discovery: Enables on-demand instruction file + discovery. + enable_file_hooks: Enables file-based hooks from ``.github/hooks/``. + enable_host_git_operations: Enables git operations on the host filesystem. + enable_session_store: Enables the cross-session store. + enable_skills: Enables skill loading. skill_directories: Directories to search for skills. instruction_directories: Additional directories to search for custom instruction files. @@ -2246,6 +2321,16 @@ async def resume_session( _validate_tool_filter_list("excluded_tools", excluded_tools) system_message = _system_message_for_mode(mode, system_message) enable_session_telemetry = _enable_session_telemetry_default(mode, enable_session_telemetry) + skip_embedding_retrieval = _skip_embedding_retrieval_default(mode, skip_embedding_retrieval) + enable_on_demand_instruction_discovery = _enable_on_demand_instruction_discovery_default( + mode, enable_on_demand_instruction_discovery + ) + enable_file_hooks = _enable_file_hooks_default(mode, enable_file_hooks) + enable_host_git_operations = _enable_host_git_operations_default( + mode, enable_host_git_operations + ) + enable_session_store = _enable_session_store_default(mode, enable_session_store) + enable_skills = _enable_skills_default(mode, enable_skills) payload: dict[str, Any] = {"sessionId": session_id} @@ -2319,6 +2404,20 @@ async def resume_session( payload["configDir"] = config_directory if enable_config_discovery is not None: payload["enableConfigDiscovery"] = enable_config_discovery + if skip_embedding_retrieval is not None: + payload["skipEmbeddingRetrieval"] = skip_embedding_retrieval + if organization_custom_instructions is not None: + payload["organizationCustomInstructions"] = organization_custom_instructions + if enable_on_demand_instruction_discovery is not None: + payload["enableOnDemandInstructionDiscovery"] = enable_on_demand_instruction_discovery + if enable_file_hooks is not None: + payload["enableFileHooks"] = enable_file_hooks + if enable_host_git_operations is not None: + payload["enableHostGitOperations"] = enable_host_git_operations + if enable_session_store is not None: + payload["enableSessionStore"] = enable_session_store + if enable_skills is not None: + payload["enableSkills"] = enable_skills if continue_pending_work is not None: payload["continuePendingWork"] = continue_pending_work @@ -2330,6 +2429,9 @@ async def resume_session( mcp_oauth_token_storage = _mcp_oauth_token_storage_default(mode, mcp_oauth_token_storage) if mcp_oauth_token_storage is not None: payload["mcpOAuthTokenStorage"] = mcp_oauth_token_storage + embedding_cache_storage = _embedding_cache_storage_default(mode, embedding_cache_storage) + if embedding_cache_storage is not None: + payload["embeddingCacheStorage"] = embedding_cache_storage payload["envValueMode"] = "direct" if custom_agents: diff --git a/python/test_tool_set.py b/python/test_tool_set.py index 3d80d29e8..6a65e0df2 100644 --- a/python/test_tool_set.py +++ b/python/test_tool_set.py @@ -6,10 +6,17 @@ from copilot import BUILTIN_TOOLS_ISOLATED, CopilotClient, ToolSet, UriRuntimeConnection from copilot._mode import ( + _embedding_cache_storage_default, + _enable_file_hooks_default, + _enable_host_git_operations_default, + _enable_on_demand_instruction_discovery_default, + _enable_session_store_default, _enable_session_telemetry_default, + _enable_skills_default, _post_create_options_patch, _require_available_tools_for_empty_mode, _require_storage_for_empty_mode, + _skip_embedding_retrieval_default, _system_message_for_mode, _validate_tool_filter_list, ) @@ -167,15 +174,65 @@ def test_empty_mode_append_promoted_to_customize(self): assert out["sections"]["environment_context"] == {"action": "remove"} -class TestTelemetryDefault: - def test_empty_mode_defaults_to_false(self): - assert _enable_session_telemetry_default("empty", None) is False +class TestEmptyModeEmbeddingCacheStorageDefaults: + def test_empty_mode_defaults_to_in_memory(self): + assert _embedding_cache_storage_default("empty", None) == "in-memory" - def test_empty_mode_caller_wins(self): - assert _enable_session_telemetry_default("empty", True) is True + def test_caller_wins(self): + assert _embedding_cache_storage_default("empty", "persistent") == "persistent" + assert _embedding_cache_storage_default("empty", "in-memory") == "in-memory" def test_copilot_cli_does_not_change(self): - assert _enable_session_telemetry_default("copilot-cli", None) is None + assert _embedding_cache_storage_default("copilot-cli", None) is None + assert _embedding_cache_storage_default("copilot-cli", "persistent") == "persistent" + + +class TestEmptyModeBooleanDefaults: + @pytest.mark.parametrize( + ("helper", "empty_default"), + [ + (_enable_session_telemetry_default, False), + (_skip_embedding_retrieval_default, True), + (_enable_on_demand_instruction_discovery_default, False), + (_enable_file_hooks_default, False), + (_enable_host_git_operations_default, False), + (_enable_session_store_default, False), + (_enable_skills_default, False), + ], + ) + def test_empty_mode_defaults(self, helper, empty_default): + assert helper("empty", None) is empty_default + + @pytest.mark.parametrize( + "helper", + [ + _enable_session_telemetry_default, + _skip_embedding_retrieval_default, + _enable_on_demand_instruction_discovery_default, + _enable_file_hooks_default, + _enable_host_git_operations_default, + _enable_session_store_default, + _enable_skills_default, + ], + ) + def test_caller_wins(self, helper): + assert helper("empty", True) is True + assert helper("empty", False) is False + + @pytest.mark.parametrize( + "helper", + [ + _enable_session_telemetry_default, + _skip_embedding_retrieval_default, + _enable_on_demand_instruction_discovery_default, + _enable_file_hooks_default, + _enable_host_git_operations_default, + _enable_session_store_default, + _enable_skills_default, + ], + ) + def test_copilot_cli_does_not_change(self, helper): + assert helper("copilot-cli", None) is None class TestPostCreatePatch: diff --git a/rust/src/session.rs b/rust/src/session.rs index eb6689ff7..7ce16125f 100644 --- a/rust/src/session.rs +++ b/rust/src/session.rs @@ -836,12 +836,35 @@ impl Client { crate::mode::validate_tool_filter_list("excluded_tools", config.excluded_tools.as_deref())?; config.system_message = crate::mode::system_message_for_mode(mode, config.system_message.take()); - if mode == crate::ClientMode::Empty && config.enable_session_telemetry.is_none() { - config.enable_session_telemetry = Some(false); + if mode == crate::ClientMode::Empty { + if config.enable_session_telemetry.is_none() { + config.enable_session_telemetry = Some(false); + } + if config.skip_embedding_retrieval.is_none() { + config.skip_embedding_retrieval = Some(true); + } + if config.enable_on_demand_instruction_discovery.is_none() { + config.enable_on_demand_instruction_discovery = Some(false); + } + if config.enable_file_hooks.is_none() { + config.enable_file_hooks = Some(false); + } + if config.enable_host_git_operations.is_none() { + config.enable_host_git_operations = Some(false); + } + if config.enable_session_store.is_none() { + config.enable_session_store = Some(false); + } + if config.enable_skills.is_none() { + config.enable_skills = Some(false); + } } if mode == crate::ClientMode::Empty && config.mcp_oauth_token_storage.is_none() { config.mcp_oauth_token_storage = Some("in-memory".into()); } + if mode == crate::ClientMode::Empty && config.embedding_cache_storage.is_none() { + config.embedding_cache_storage = Some("in-memory".into()); + } let opt_skip_custom_instructions = config.skip_custom_instructions; let opt_custom_agents_local_only = config.custom_agents_local_only; let opt_coauthor_enabled = config.coauthor_enabled; @@ -1066,12 +1089,35 @@ impl Client { crate::mode::validate_tool_filter_list("excluded_tools", config.excluded_tools.as_deref())?; config.system_message = crate::mode::system_message_for_mode(mode, config.system_message.take()); - if mode == crate::ClientMode::Empty && config.enable_session_telemetry.is_none() { - config.enable_session_telemetry = Some(false); + if mode == crate::ClientMode::Empty { + if config.enable_session_telemetry.is_none() { + config.enable_session_telemetry = Some(false); + } + if config.skip_embedding_retrieval.is_none() { + config.skip_embedding_retrieval = Some(true); + } + if config.enable_on_demand_instruction_discovery.is_none() { + config.enable_on_demand_instruction_discovery = Some(false); + } + if config.enable_file_hooks.is_none() { + config.enable_file_hooks = Some(false); + } + if config.enable_host_git_operations.is_none() { + config.enable_host_git_operations = Some(false); + } + if config.enable_session_store.is_none() { + config.enable_session_store = Some(false); + } + if config.enable_skills.is_none() { + config.enable_skills = Some(false); + } } if mode == crate::ClientMode::Empty && config.mcp_oauth_token_storage.is_none() { config.mcp_oauth_token_storage = Some("in-memory".into()); } + if mode == crate::ClientMode::Empty && config.embedding_cache_storage.is_none() { + config.embedding_cache_storage = Some("in-memory".into()); + } let opt_skip_custom_instructions = config.skip_custom_instructions; let opt_custom_agents_local_only = config.custom_agents_local_only; let opt_coauthor_enabled = config.coauthor_enabled; diff --git a/rust/src/types.rs b/rust/src/types.rs index d63ede83b..f902f8301 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -1193,6 +1193,23 @@ pub struct SessionConfig { pub mcp_oauth_token_storage: Option, /// When true, the CLI runs config discovery (MCP config files, skills, plugins). pub enable_config_discovery: Option, + /// When true, skips embedding retrieval for this session. + pub skip_embedding_retrieval: Option, + /// Controls how the embedding cache is stored for this session. + /// `"persistent"` caches on disk; `"in-memory"` discards when session ends. + pub embedding_cache_storage: Option, + /// Organization-level custom instructions to apply to this session. + pub organization_custom_instructions: Option, + /// When true, enables on-demand instruction discovery for this session. + pub enable_on_demand_instruction_discovery: Option, + /// When true, enables file hooks for this session. + pub enable_file_hooks: Option, + /// When true, allows host Git operations for this session. + pub enable_host_git_operations: Option, + /// When true, enables the session store for this session. + pub enable_session_store: Option, + /// When true, enables skills for this session. + pub enable_skills: Option, /// **Experimental.** This option is part of an experimental wire-protocol /// surface (SEP-1865) and may change or be removed in a future release. /// @@ -1368,7 +1385,27 @@ impl std::fmt::Debug for SessionConfig { .field("excluded_tools", &self.excluded_tools) .field("mcp_servers", &self.mcp_servers) .field("mcp_oauth_token_storage", &self.mcp_oauth_token_storage) + .field("embedding_cache_storage", &self.embedding_cache_storage) .field("enable_config_discovery", &self.enable_config_discovery) + .field("skip_embedding_retrieval", &self.skip_embedding_retrieval) + .field( + "organization_custom_instructions", + &self + .organization_custom_instructions + .as_ref() + .map(|_| ""), + ) + .field( + "enable_on_demand_instruction_discovery", + &self.enable_on_demand_instruction_discovery, + ) + .field("enable_file_hooks", &self.enable_file_hooks) + .field( + "enable_host_git_operations", + &self.enable_host_git_operations, + ) + .field("enable_session_store", &self.enable_session_store) + .field("enable_skills", &self.enable_skills) .field("enable_mcp_apps", &self.enable_mcp_apps) .field("skill_directories", &self.skill_directories) .field("instruction_directories", &self.instruction_directories) @@ -1458,6 +1495,14 @@ impl Default for SessionConfig { mcp_servers: None, mcp_oauth_token_storage: None, enable_config_discovery: None, + skip_embedding_retrieval: None, + organization_custom_instructions: None, + enable_on_demand_instruction_discovery: None, + enable_file_hooks: None, + enable_host_git_operations: None, + enable_session_store: None, + enable_skills: None, + embedding_cache_storage: None, enable_mcp_apps: None, skill_directories: None, instruction_directories: None, @@ -1583,8 +1628,16 @@ impl SessionConfig { tool_filter_precedence: "excluded", mcp_servers: self.mcp_servers, mcp_oauth_token_storage: self.mcp_oauth_token_storage, + embedding_cache_storage: self.embedding_cache_storage, env_value_mode: "direct", enable_config_discovery: self.enable_config_discovery, + skip_embedding_retrieval: self.skip_embedding_retrieval, + organization_custom_instructions: self.organization_custom_instructions, + enable_on_demand_instruction_discovery: self.enable_on_demand_instruction_discovery, + enable_file_hooks: self.enable_file_hooks, + enable_host_git_operations: self.enable_host_git_operations, + enable_session_store: self.enable_session_store, + enable_skills: self.enable_skills, request_user_input, request_permission: permission_active, request_exit_plan_mode, @@ -1851,12 +1904,66 @@ impl SessionConfig { self } + /// Set embedding cache storage mode. + pub fn with_embedding_cache_storage( + mut self, + embedding_cache_storage: impl Into, + ) -> Self { + self.embedding_cache_storage = Some(embedding_cache_storage.into()); + self + } + /// Enable or disable CLI config discovery (MCP config files, skills, plugins). pub fn with_enable_config_discovery(mut self, enable: bool) -> Self { self.enable_config_discovery = Some(enable); self } + /// Set [`Self::skip_embedding_retrieval`]. + pub fn with_skip_embedding_retrieval(mut self, value: bool) -> Self { + self.skip_embedding_retrieval = Some(value); + self + } + + /// Set [`Self::organization_custom_instructions`]. + pub fn with_organization_custom_instructions( + mut self, + instructions: impl Into, + ) -> Self { + self.organization_custom_instructions = Some(instructions.into()); + self + } + + /// Set [`Self::enable_on_demand_instruction_discovery`]. + pub fn with_enable_on_demand_instruction_discovery(mut self, value: bool) -> Self { + self.enable_on_demand_instruction_discovery = Some(value); + self + } + + /// Set [`Self::enable_file_hooks`]. + pub fn with_enable_file_hooks(mut self, value: bool) -> Self { + self.enable_file_hooks = Some(value); + self + } + + /// Set [`Self::enable_host_git_operations`]. + pub fn with_enable_host_git_operations(mut self, value: bool) -> Self { + self.enable_host_git_operations = Some(value); + self + } + + /// Set [`Self::enable_session_store`]. + pub fn with_enable_session_store(mut self, value: bool) -> Self { + self.enable_session_store = Some(value); + self + } + + /// Set [`Self::enable_skills`]. + pub fn with_enable_skills(mut self, value: bool) -> Self { + self.enable_skills = Some(value); + self + } + /// **Experimental.** This method is part of an experimental wire-protocol /// surface (SEP-1865) and may change or be removed in a future release. /// @@ -2087,6 +2194,22 @@ pub struct ResumeSessionConfig { pub mcp_oauth_token_storage: Option, /// Enable config discovery on resume. pub enable_config_discovery: Option, + /// When true, skips embedding retrieval on resume. + pub skip_embedding_retrieval: Option, + /// Controls how the embedding cache is stored for this session. + pub embedding_cache_storage: Option, + /// Organization-level custom instructions to apply on resume. + pub organization_custom_instructions: Option, + /// When true, enables on-demand instruction discovery on resume. + pub enable_on_demand_instruction_discovery: Option, + /// When true, enables file hooks on resume. + pub enable_file_hooks: Option, + /// When true, allows host Git operations on resume. + pub enable_host_git_operations: Option, + /// When true, enables the session store on resume. + pub enable_session_store: Option, + /// When true, enables skills on resume. + pub enable_skills: Option, /// **Experimental.** This option is part of an experimental wire-protocol /// surface (SEP-1865) and may change or be removed in a future release. /// @@ -2212,7 +2335,27 @@ impl std::fmt::Debug for ResumeSessionConfig { .field("excluded_tools", &self.excluded_tools) .field("mcp_servers", &self.mcp_servers) .field("mcp_oauth_token_storage", &self.mcp_oauth_token_storage) + .field("embedding_cache_storage", &self.embedding_cache_storage) .field("enable_config_discovery", &self.enable_config_discovery) + .field("skip_embedding_retrieval", &self.skip_embedding_retrieval) + .field( + "organization_custom_instructions", + &self + .organization_custom_instructions + .as_ref() + .map(|_| ""), + ) + .field( + "enable_on_demand_instruction_discovery", + &self.enable_on_demand_instruction_discovery, + ) + .field("enable_file_hooks", &self.enable_file_hooks) + .field( + "enable_host_git_operations", + &self.enable_host_git_operations, + ) + .field("enable_session_store", &self.enable_session_store) + .field("enable_skills", &self.enable_skills) .field("enable_mcp_apps", &self.enable_mcp_apps) .field("skill_directories", &self.skill_directories) .field("instruction_directories", &self.instruction_directories) @@ -2339,8 +2482,16 @@ impl ResumeSessionConfig { tool_filter_precedence: "excluded", mcp_servers: self.mcp_servers, mcp_oauth_token_storage: self.mcp_oauth_token_storage, + embedding_cache_storage: self.embedding_cache_storage, env_value_mode: "direct", enable_config_discovery: self.enable_config_discovery, + skip_embedding_retrieval: self.skip_embedding_retrieval, + organization_custom_instructions: self.organization_custom_instructions, + enable_on_demand_instruction_discovery: self.enable_on_demand_instruction_discovery, + enable_file_hooks: self.enable_file_hooks, + enable_host_git_operations: self.enable_host_git_operations, + enable_session_store: self.enable_session_store, + enable_skills: self.enable_skills, request_user_input, request_permission: permission_active, request_exit_plan_mode, @@ -2412,6 +2563,14 @@ impl ResumeSessionConfig { mcp_servers: None, mcp_oauth_token_storage: None, enable_config_discovery: None, + skip_embedding_retrieval: None, + organization_custom_instructions: None, + enable_on_demand_instruction_discovery: None, + enable_file_hooks: None, + enable_host_git_operations: None, + enable_session_store: None, + enable_skills: None, + embedding_cache_storage: None, enable_mcp_apps: None, skill_directories: None, instruction_directories: None, @@ -2647,12 +2806,66 @@ impl ResumeSessionConfig { self } + /// Set embedding cache storage mode on resume. + pub fn with_embedding_cache_storage( + mut self, + embedding_cache_storage: impl Into, + ) -> Self { + self.embedding_cache_storage = Some(embedding_cache_storage.into()); + self + } + /// Enable or disable CLI config discovery on resume. pub fn with_enable_config_discovery(mut self, enable: bool) -> Self { self.enable_config_discovery = Some(enable); self } + /// Set [`Self::skip_embedding_retrieval`]. + pub fn with_skip_embedding_retrieval(mut self, value: bool) -> Self { + self.skip_embedding_retrieval = Some(value); + self + } + + /// Set [`Self::organization_custom_instructions`]. + pub fn with_organization_custom_instructions( + mut self, + instructions: impl Into, + ) -> Self { + self.organization_custom_instructions = Some(instructions.into()); + self + } + + /// Set [`Self::enable_on_demand_instruction_discovery`]. + pub fn with_enable_on_demand_instruction_discovery(mut self, value: bool) -> Self { + self.enable_on_demand_instruction_discovery = Some(value); + self + } + + /// Set [`Self::enable_file_hooks`]. + pub fn with_enable_file_hooks(mut self, value: bool) -> Self { + self.enable_file_hooks = Some(value); + self + } + + /// Set [`Self::enable_host_git_operations`]. + pub fn with_enable_host_git_operations(mut self, value: bool) -> Self { + self.enable_host_git_operations = Some(value); + self + } + + /// Set [`Self::enable_session_store`]. + pub fn with_enable_session_store(mut self, value: bool) -> Self { + self.enable_session_store = Some(value); + self + } + + /// Set [`Self::enable_skills`]. + pub fn with_enable_skills(mut self, value: bool) -> Self { + self.enable_skills = Some(value); + self + } + /// **Experimental.** This method is part of an experimental wire-protocol /// surface (SEP-1865) and may change or be removed in a future release. /// diff --git a/rust/src/wire.rs b/rust/src/wire.rs index 60a7b409b..05f44aabf 100644 --- a/rust/src/wire.rs +++ b/rust/src/wire.rs @@ -79,9 +79,25 @@ pub(crate) struct SessionCreateWire { pub mcp_servers: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub mcp_oauth_token_storage: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub embedding_cache_storage: Option, pub env_value_mode: &'static str, #[serde(skip_serializing_if = "Option::is_none")] pub enable_config_discovery: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub skip_embedding_retrieval: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub organization_custom_instructions: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_on_demand_instruction_discovery: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_file_hooks: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_host_git_operations: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_session_store: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_skills: Option, pub request_user_input: bool, pub request_permission: bool, pub request_exit_plan_mode: bool, @@ -166,9 +182,25 @@ pub(crate) struct SessionResumeWire { pub mcp_servers: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub mcp_oauth_token_storage: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub embedding_cache_storage: Option, pub env_value_mode: &'static str, #[serde(skip_serializing_if = "Option::is_none")] pub enable_config_discovery: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub skip_embedding_retrieval: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub organization_custom_instructions: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_on_demand_instruction_discovery: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_file_hooks: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_host_git_operations: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_session_store: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_skills: Option, pub request_user_input: bool, pub request_permission: bool, pub request_exit_plan_mode: bool,