From dca382965552633f70602c6c091f9f9625909823 Mon Sep 17 00:00:00 2001 From: Devraj Mehta Date: Wed, 14 Jan 2026 23:09:37 -0500 Subject: [PATCH 1/3] Add config dir to session options --- dotnet/src/Client.cs | 6 ++++-- dotnet/src/Types.cs | 7 +++++++ go/client.go | 4 ++++ go/types.go | 3 +++ nodejs/src/client.ts | 1 + nodejs/src/types.ts | 6 ++++++ python/copilot/client.py | 5 +++++ python/copilot/types.py | 3 +++ 8 files changed, 33 insertions(+), 2 deletions(-) diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 87cab01a..465955bd 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -343,7 +343,8 @@ public async Task CreateSessionAsync(SessionConfig? config = nul config?.OnPermissionRequest != null ? true : null, config?.Streaming == true ? true : null, config?.McpServers, - config?.CustomAgents); + config?.CustomAgents, + config?.ConfigDir); var response = await connection.Rpc.InvokeWithCancellationAsync( "session.create", [request], cancellationToken); @@ -925,7 +926,8 @@ private record CreateSessionRequest( bool? RequestPermission, bool? Streaming, Dictionary? McpServers, - List? CustomAgents); + List? CustomAgents, + string? ConfigDir); private record ToolDefinition( string Name, diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index a5dc5538..0a4bd4f5 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -293,6 +293,13 @@ public class SessionConfig { public string? SessionId { get; set; } public string? Model { get; set; } + + /// + /// Override the default configuration directory location. + /// When specified, the session will use this directory for storing config and state. + /// + public string? ConfigDir { get; set; } + public ICollection? Tools { get; set; } public SystemMessageConfig? SystemMessage { get; set; } public List? AvailableTools { get; set; } diff --git a/go/client.go b/go/client.go index ca06335d..4b70c33d 100644 --- a/go/client.go +++ b/go/client.go @@ -534,6 +534,10 @@ func (c *Client) CreateSession(config *SessionConfig) (*Session, error) { } params["customAgents"] = customAgents } + // Add config directory override + if config.ConfigDir != "" { + params["configDir"] = config.ConfigDir + } } result, err := c.client.Request("session.create", params) diff --git a/go/types.go b/go/types.go index d4883206..7334dcb2 100644 --- a/go/types.go +++ b/go/types.go @@ -144,6 +144,9 @@ type SessionConfig struct { SessionID string // Model to use for this session Model string + // ConfigDir overrides the default configuration directory location. + // When specified, the session will use this directory for storing config and state. + ConfigDir string // Tools exposes caller-implemented tools to the CLI Tools []Tool // SystemMessage configures system message customization diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index b1f20f84..6fd941f1 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -446,6 +446,7 @@ export class CopilotClient { streaming: config.streaming, mcpServers: config.mcpServers, customAgents: config.customAgents, + configDir: config.configDir, }); const sessionId = (response as { sessionId: string }).sessionId; diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 6c20cfb1..a57a9e5e 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -324,6 +324,12 @@ export interface SessionConfig { */ model?: string; + /** + * Override the default configuration directory location. + * When specified, the session will use this directory for storing config and state. + */ + configDir?: string; + /** * Tools exposed to the CLI server */ diff --git a/python/copilot/client.py b/python/copilot/client.py index 0828e6ec..6aae7056 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -400,6 +400,11 @@ async def create_session(self, config: Optional[SessionConfig] = None) -> Copilo self._convert_custom_agent_to_wire_format(agent) for agent in custom_agents ] + # Add config directory override if provided + config_dir = cfg.get("config_dir") + if config_dir: + payload["configDir"] = config_dir + if not self._client: raise RuntimeError("Client not connected") response = await self._client.request("session.create", payload) diff --git a/python/copilot/types.py b/python/copilot/types.py index 782bc200..1e1dabc1 100644 --- a/python/copilot/types.py +++ b/python/copilot/types.py @@ -216,6 +216,9 @@ class SessionConfig(TypedDict, total=False): mcp_servers: Dict[str, MCPServerConfig] # Custom agent configurations for the session custom_agents: List[CustomAgentConfig] + # Override the default configuration directory location. + # When specified, the session will use this directory for storing config and state. + config_dir: str # Azure-specific provider options From 345c59bf86c590272b571dbe06d67f9800f56170 Mon Sep 17 00:00:00 2001 From: Devraj Mehta Date: Thu, 15 Jan 2026 00:14:14 -0500 Subject: [PATCH 2/3] add tests --- dotnet/test/SessionTests.cs | 15 +++++++++ go/e2e/session_test.go | 32 +++++++++++++++++++ nodejs/test/e2e/session.test.ts | 14 ++++++++ python/e2e/test_session.py | 13 ++++++++ ...create_session_with_custom_config_dir.yaml | 10 ++++++ 5 files changed, 84 insertions(+) create mode 100644 test/snapshots/session/should_create_session_with_custom_config_dir.yaml diff --git a/dotnet/test/SessionTests.cs b/dotnet/test/SessionTests.cs index 2e1119f5..e63a43e4 100644 --- a/dotnet/test/SessionTests.cs +++ b/dotnet/test/SessionTests.cs @@ -371,4 +371,19 @@ public async Task SendAndWait_Throws_On_Timeout() Assert.Contains("timed out", ex.Message); } + + [Fact] + public async Task Should_Create_Session_With_Custom_Config_Dir() + { + var customConfigDir = Path.Combine(Ctx.HomeDir, "custom-config"); + var session = await Client.CreateSessionAsync(new SessionConfig { ConfigDir = customConfigDir }); + + Assert.Matches(@"^[a-f0-9-]+$", session.SessionId); + + // Session should work normally with custom config dir + await session.SendAsync(new MessageOptions { Prompt = "What is 1+1?" }); + var assistantMessage = await TestHelper.GetFinalAssistantMessageAsync(session); + Assert.NotNull(assistantMessage); + Assert.Contains("2", assistantMessage!.Data.Content); + } } diff --git a/go/e2e/session_test.go b/go/e2e/session_test.go index 3de45eb5..fd24f759 100644 --- a/go/e2e/session_test.go +++ b/go/e2e/session_test.go @@ -672,6 +672,38 @@ func TestSession(t *testing.T) { t.Errorf("Expected assistant message to contain '300', got %v", assistantMessage.Data.Content) } }) + + t.Run("should create session with custom config dir", func(t *testing.T) { + ctx.ConfigureForTest(t) + + customConfigDir := ctx.HomeDir + "/custom-config" + session, err := client.CreateSession(&copilot.SessionConfig{ + ConfigDir: customConfigDir, + }) + if err != nil { + t.Fatalf("Failed to create session with custom config dir: %v", err) + } + + matched, _ := regexp.MatchString(`^[a-f0-9-]+$`, session.SessionID) + if !matched { + t.Errorf("Expected session ID to match UUID pattern, got %q", session.SessionID) + } + + // Session should work normally with custom config dir + _, err = session.Send(copilot.MessageOptions{Prompt: "What is 1+1?"}) + if err != nil { + t.Fatalf("Failed to send message: %v", err) + } + + assistantMessage, err := testharness.GetFinalAssistantMessage(session, 60*time.Second) + if err != nil { + t.Fatalf("Failed to get assistant message: %v", err) + } + + if assistantMessage.Data.Content == nil || !strings.Contains(*assistantMessage.Data.Content, "2") { + t.Errorf("Expected assistant message to contain '2', got %v", assistantMessage.Data.Content) + } + }) } func getSystemMessage(exchange testharness.ParsedHttpExchange) string { diff --git a/nodejs/test/e2e/session.test.ts b/nodejs/test/e2e/session.test.ts index 6779b004..4b8f3b70 100644 --- a/nodejs/test/e2e/session.test.ts +++ b/nodejs/test/e2e/session.test.ts @@ -313,6 +313,20 @@ describe("Sessions", async () => { // Verify the assistant response contains the expected answer expect(assistantMessage?.data.content).toContain("300"); }); + + it("should create session with custom config dir", async () => { + const customConfigDir = `${homeDir}/custom-config`; + const session = await client.createSession({ + configDir: customConfigDir, + }); + + expect(session.sessionId).toMatch(/^[a-f0-9-]+$/); + + // Session should work normally with custom config dir + await session.send({ prompt: "What is 1+1?" }); + const assistantMessage = await getFinalAssistantMessage(session); + expect(assistantMessage.data.content).toContain("2"); + }); }); function getSystemMessage(exchange: ParsedHttpExchange): string | undefined { diff --git a/python/e2e/test_session.py b/python/e2e/test_session.py index e54465e1..97afddaf 100644 --- a/python/e2e/test_session.py +++ b/python/e2e/test_session.py @@ -362,6 +362,19 @@ def on_event(event): assistant_message = await get_final_assistant_message(session) assert "300" in assistant_message.data.content + async def test_should_create_session_with_custom_config_dir(self, ctx: E2ETestContext): + import os + + custom_config_dir = os.path.join(ctx.home_dir, "custom-config") + session = await ctx.client.create_session({"config_dir": custom_config_dir}) + + assert session.session_id + + # Session should work normally with custom config dir + await session.send({"prompt": "What is 1+1?"}) + assistant_message = await get_final_assistant_message(session) + assert "2" in assistant_message.data.content + def _get_system_message(exchange: dict) -> str: messages = exchange.get("request", {}).get("messages", []) diff --git a/test/snapshots/session/should_create_session_with_custom_config_dir.yaml b/test/snapshots/session/should_create_session_with_custom_config_dir.yaml new file mode 100644 index 00000000..25040210 --- /dev/null +++ b/test/snapshots/session/should_create_session_with_custom_config_dir.yaml @@ -0,0 +1,10 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: What is 1+1? + - role: assistant + content: 1 + 1 = 2 From 5c0b6e96b866d6df7eeecb02ea54c6ff057551d3 Mon Sep 17 00:00:00 2001 From: Devraj Mehta Date: Tue, 20 Jan 2026 09:11:18 -0500 Subject: [PATCH 3/3] Use join --- dotnet/test/SessionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/test/SessionTests.cs b/dotnet/test/SessionTests.cs index e63a43e4..306d333a 100644 --- a/dotnet/test/SessionTests.cs +++ b/dotnet/test/SessionTests.cs @@ -375,7 +375,7 @@ public async Task SendAndWait_Throws_On_Timeout() [Fact] public async Task Should_Create_Session_With_Custom_Config_Dir() { - var customConfigDir = Path.Combine(Ctx.HomeDir, "custom-config"); + var customConfigDir = Path.Join(Ctx.HomeDir, "custom-config"); var session = await Client.CreateSessionAsync(new SessionConfig { ConfigDir = customConfigDir }); Assert.Matches(@"^[a-f0-9-]+$", session.SessionId);