From 6e209beea886bb6eedff8ab63c6644a181adb3c4 Mon Sep 17 00:00:00 2001 From: Patrick Nikoletich Date: Sat, 7 Mar 2026 20:54:55 -0800 Subject: [PATCH 1/6] Add reasoningEffort to setModel/session.model.switchTo across all SDKs All four SDKs now support passing reasoningEffort when switching models mid-session via setModel(). The parameter is optional and backward-compatible. - Node.js: setModel(model, { reasoningEffort? }) - Python: set_model(model, *, reasoning_effort=None) - Go: SetModel(ctx, model, opts ...*SetModelOptions) - .NET: SetModelAsync(model, reasoningEffort?, cancellationToken?) Fixes #687 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Session.cs | 14 ++++++++++++-- dotnet/test/RpcTests.cs | 2 +- go/internal/e2e/rpc_test.go | 2 +- go/session.go | 21 ++++++++++++++++++--- nodejs/src/session.ts | 9 +++++++-- nodejs/test/client.test.ts | 25 +++++++++++++++++++++++++ python/copilot/session.py | 12 ++++++++++-- 7 files changed, 74 insertions(+), 11 deletions(-) diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 07a818c2..489f21b1 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -720,15 +720,25 @@ await InvokeRpcAsync( /// The new model takes effect for the next message. Conversation history is preserved. /// /// Model ID to switch to (e.g., "gpt-4.1"). + /// Optional reasoning effort level (e.g., "low", "medium", "high", "xhigh"). /// Optional cancellation token. /// /// /// await session.SetModelAsync("gpt-4.1"); + /// await session.SetModelAsync("claude-sonnet-4.6", SessionModelSwitchToRequestReasoningEffort.High); /// /// - public async Task SetModelAsync(string model, CancellationToken cancellationToken = default) + public async Task SetModelAsync(string model, SessionModelSwitchToRequestReasoningEffort? reasoningEffort = null, CancellationToken cancellationToken = default) { - await Rpc.Model.SwitchToAsync(model, cancellationToken: cancellationToken); + await Rpc.Model.SwitchToAsync(model, reasoningEffort, cancellationToken); + } + + /// + /// Changes the model for this session (backward-compatible overload). + /// + public Task SetModelAsync(string model, CancellationToken cancellationToken) + { + return SetModelAsync(model, reasoningEffort: null, cancellationToken); } /// diff --git a/dotnet/test/RpcTests.cs b/dotnet/test/RpcTests.cs index a1369558..7e3c862b 100644 --- a/dotnet/test/RpcTests.cs +++ b/dotnet/test/RpcTests.cs @@ -73,7 +73,7 @@ public async Task Should_Call_Session_Rpc_Model_SwitchTo() Assert.NotNull(before.ModelId); // Switch to a different model - var result = await session.Rpc.Model.SwitchToAsync(modelId: "gpt-4.1"); + var result = await session.Rpc.Model.SwitchToAsync(modelId: "gpt-4.1", reasoningEffort: null); Assert.Equal("gpt-4.1", result.ModelId); // Verify the switch persisted diff --git a/go/internal/e2e/rpc_test.go b/go/internal/e2e/rpc_test.go index 61a5e338..ac4a5b0d 100644 --- a/go/internal/e2e/rpc_test.go +++ b/go/internal/e2e/rpc_test.go @@ -201,7 +201,7 @@ func TestSessionRpc(t *testing.T) { t.Fatalf("Failed to create session: %v", err) } - if err := session.SetModel(t.Context(), "gpt-4.1"); err != nil { + if err := session.SetModel(t.Context(), "gpt-4.1", nil); err != nil { t.Fatalf("SetModel returned error: %v", err) } }) diff --git a/go/session.go b/go/session.go index f7a1ba4c..061bb5c5 100644 --- a/go/session.go +++ b/go/session.go @@ -737,16 +737,31 @@ func (s *Session) Abort(ctx context.Context) error { return nil } +// SetModelOptions configures optional parameters for SetModel. +type SetModelOptions struct { + // ReasoningEffort sets the reasoning effort level for the new model (e.g., "low", "medium", "high", "xhigh"). + ReasoningEffort rpc.ReasoningEffort +} + // SetModel changes the model for this session. // The new model takes effect for the next message. Conversation history is preserved. +// Pass nil for opts if no additional options are needed. // // Example: // -// if err := session.SetModel(context.Background(), "gpt-4.1"); err != nil { +// if err := session.SetModel(context.Background(), "gpt-4.1", nil); err != nil { // log.Printf("Failed to set model: %v", err) // } -func (s *Session) SetModel(ctx context.Context, model string) error { - _, err := s.RPC.Model.SwitchTo(ctx, &rpc.SessionModelSwitchToParams{ModelID: model}) +// if err := session.SetModel(context.Background(), "claude-sonnet-4.6", &SetModelOptions{ReasoningEffort: "high"}); err != nil { +// log.Printf("Failed to set model: %v", err) +// } +func (s *Session) SetModel(ctx context.Context, model string, opts *SetModelOptions) error { + params := &rpc.SessionModelSwitchToParams{ModelID: model} + if opts != nil && opts.ReasoningEffort != "" { + re := opts.ReasoningEffort + params.ReasoningEffort = &re + } + _, err := s.RPC.Model.SwitchTo(ctx, params) if err != nil { return fmt.Errorf("failed to set model: %w", err) } diff --git a/nodejs/src/session.ts b/nodejs/src/session.ts index ed08326b..93b5661c 100644 --- a/nodejs/src/session.ts +++ b/nodejs/src/session.ts @@ -718,14 +718,19 @@ export class CopilotSession { * The new model takes effect for the next message. Conversation history is preserved. * * @param model - Model ID to switch to + * @param options - Optional settings for the new model * * @example * ```typescript * await session.setModel("gpt-4.1"); + * await session.setModel("claude-sonnet-4.6", { reasoningEffort: "high" }); * ``` */ - async setModel(model: string): Promise { - await this.rpc.model.switchTo({ modelId: model }); + async setModel( + model: string, + options?: { reasoningEffort?: "low" | "medium" | "high" | "xhigh" } + ): Promise { + await this.rpc.model.switchTo({ modelId: model, ...options }); } /** diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts index c8ae9488..3d13d27f 100644 --- a/nodejs/test/client.test.ts +++ b/nodejs/test/client.test.ts @@ -123,6 +123,31 @@ describe("CopilotClient", () => { spy.mockRestore(); }); + it("sends reasoningEffort with session.model.switchTo when provided", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const session = await client.createSession({ onPermissionRequest: approveAll }); + + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, _params: any) => { + if (method === "session.model.switchTo") return {}; + throw new Error(`Unexpected method: ${method}`); + }); + + await session.setModel("claude-sonnet-4.6", { reasoningEffort: "high" }); + + expect(spy).toHaveBeenCalledWith("session.model.switchTo", { + sessionId: session.sessionId, + modelId: "claude-sonnet-4.6", + reasoningEffort: "high", + }); + + spy.mockRestore(); + }); + describe("URL parsing", () => { it("should parse port-only URL format", () => { const client = new CopilotClient({ diff --git a/python/copilot/session.py b/python/copilot/session.py index ad049811..cf09cb28 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -728,7 +728,7 @@ async def abort(self) -> None: """ await self._client.request("session.abort", {"sessionId": self.session_id}) - async def set_model(self, model: str) -> None: + async def set_model(self, model: str, *, reasoning_effort: str | None = None) -> None: """ Change the model for this session. @@ -737,14 +737,22 @@ async def set_model(self, model: str) -> None: Args: model: Model ID to switch to (e.g., "gpt-4.1", "claude-sonnet-4"). + reasoning_effort: Optional reasoning effort level for the new model + (e.g., "low", "medium", "high", "xhigh"). Raises: Exception: If the session has been destroyed or the connection fails. Example: >>> await session.set_model("gpt-4.1") + >>> await session.set_model("claude-sonnet-4.6", reasoning_effort="high") """ - await self.rpc.model.switch_to(SessionModelSwitchToParams(model_id=model)) + await self.rpc.model.switch_to( + SessionModelSwitchToParams( + model_id=model, + reasoning_effort=reasoning_effort, + ) + ) async def log( self, From 3760778148ae4e9b0979c82aa38503979303abe9 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 10 Mar 2026 16:42:06 +0000 Subject: [PATCH 2/6] Improve setModel API: variadic Go opts, reuse TS ReasoningEffort type, fix .NET overloads - Go: use variadic ...SetModelOptions for backward compatibility - TypeScript: reuse ReasoningEffort type instead of inline union - .NET: remove redundant default on reasoningEffort param, make CancellationToken optional in both overloads - Revert unrelated dotnet/test/RpcTests.cs change Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Session.cs | 8 ++++---- dotnet/test/RpcTests.cs | 2 +- go/internal/e2e/rpc_test.go | 2 +- go/session.go | 11 +++++------ nodejs/src/session.ts | 3 ++- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 489f21b1..1664d072 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -720,7 +720,7 @@ await InvokeRpcAsync( /// The new model takes effect for the next message. Conversation history is preserved. /// /// Model ID to switch to (e.g., "gpt-4.1"). - /// Optional reasoning effort level (e.g., "low", "medium", "high", "xhigh"). + /// Reasoning effort level (e.g., "low", "medium", "high", "xhigh"). /// Optional cancellation token. /// /// @@ -728,15 +728,15 @@ await InvokeRpcAsync( /// await session.SetModelAsync("claude-sonnet-4.6", SessionModelSwitchToRequestReasoningEffort.High); /// /// - public async Task SetModelAsync(string model, SessionModelSwitchToRequestReasoningEffort? reasoningEffort = null, CancellationToken cancellationToken = default) + public async Task SetModelAsync(string model, SessionModelSwitchToRequestReasoningEffort? reasoningEffort, CancellationToken cancellationToken = default) { await Rpc.Model.SwitchToAsync(model, reasoningEffort, cancellationToken); } /// - /// Changes the model for this session (backward-compatible overload). + /// Changes the model for this session. /// - public Task SetModelAsync(string model, CancellationToken cancellationToken) + public Task SetModelAsync(string model, CancellationToken cancellationToken = default) { return SetModelAsync(model, reasoningEffort: null, cancellationToken); } diff --git a/dotnet/test/RpcTests.cs b/dotnet/test/RpcTests.cs index 7e3c862b..a1369558 100644 --- a/dotnet/test/RpcTests.cs +++ b/dotnet/test/RpcTests.cs @@ -73,7 +73,7 @@ public async Task Should_Call_Session_Rpc_Model_SwitchTo() Assert.NotNull(before.ModelId); // Switch to a different model - var result = await session.Rpc.Model.SwitchToAsync(modelId: "gpt-4.1", reasoningEffort: null); + var result = await session.Rpc.Model.SwitchToAsync(modelId: "gpt-4.1"); Assert.Equal("gpt-4.1", result.ModelId); // Verify the switch persisted diff --git a/go/internal/e2e/rpc_test.go b/go/internal/e2e/rpc_test.go index ac4a5b0d..61a5e338 100644 --- a/go/internal/e2e/rpc_test.go +++ b/go/internal/e2e/rpc_test.go @@ -201,7 +201,7 @@ func TestSessionRpc(t *testing.T) { t.Fatalf("Failed to create session: %v", err) } - if err := session.SetModel(t.Context(), "gpt-4.1", nil); err != nil { + if err := session.SetModel(t.Context(), "gpt-4.1"); err != nil { t.Fatalf("SetModel returned error: %v", err) } }) diff --git a/go/session.go b/go/session.go index 061bb5c5..90f5e69a 100644 --- a/go/session.go +++ b/go/session.go @@ -745,20 +745,19 @@ type SetModelOptions struct { // SetModel changes the model for this session. // The new model takes effect for the next message. Conversation history is preserved. -// Pass nil for opts if no additional options are needed. // // Example: // -// if err := session.SetModel(context.Background(), "gpt-4.1", nil); err != nil { +// if err := session.SetModel(context.Background(), "gpt-4.1"); err != nil { // log.Printf("Failed to set model: %v", err) // } -// if err := session.SetModel(context.Background(), "claude-sonnet-4.6", &SetModelOptions{ReasoningEffort: "high"}); err != nil { +// if err := session.SetModel(context.Background(), "claude-sonnet-4.6", SetModelOptions{ReasoningEffort: "high"}); err != nil { // log.Printf("Failed to set model: %v", err) // } -func (s *Session) SetModel(ctx context.Context, model string, opts *SetModelOptions) error { +func (s *Session) SetModel(ctx context.Context, model string, opts ...SetModelOptions) error { params := &rpc.SessionModelSwitchToParams{ModelID: model} - if opts != nil && opts.ReasoningEffort != "" { - re := opts.ReasoningEffort + if len(opts) > 0 && opts[0].ReasoningEffort != "" { + re := opts[0].ReasoningEffort params.ReasoningEffort = &re } _, err := s.RPC.Model.SwitchTo(ctx, params) diff --git a/nodejs/src/session.ts b/nodejs/src/session.ts index 93b5661c..f449b2ca 100644 --- a/nodejs/src/session.ts +++ b/nodejs/src/session.ts @@ -16,6 +16,7 @@ import type { PermissionHandler, PermissionRequest, PermissionRequestResult, + ReasoningEffort, SessionEvent, SessionEventHandler, SessionEventPayload, @@ -728,7 +729,7 @@ export class CopilotSession { */ async setModel( model: string, - options?: { reasoningEffort?: "low" | "medium" | "high" | "xhigh" } + options?: { reasoningEffort?: ReasoningEffort } ): Promise { await this.rpc.model.switchTo({ modelId: model, ...options }); } From 7c9430d8539c193a2241c82364190ac046da64a0 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Fri, 13 Mar 2026 16:55:19 +0000 Subject: [PATCH 3/6] fix(go): use string type for reasoning effort instead of removed rpc.ReasoningEffort The generated RPC types use *string for reasoning effort, not a custom type. Update SetModelOptions to use string instead of the non-existent rpc.ReasoningEffort type. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- go/session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/session.go b/go/session.go index 90f5e69a..d2a5785b 100644 --- a/go/session.go +++ b/go/session.go @@ -740,7 +740,7 @@ func (s *Session) Abort(ctx context.Context) error { // SetModelOptions configures optional parameters for SetModel. type SetModelOptions struct { // ReasoningEffort sets the reasoning effort level for the new model (e.g., "low", "medium", "high", "xhigh"). - ReasoningEffort rpc.ReasoningEffort + ReasoningEffort string } // SetModel changes the model for this session. From 88c58671cea2ec94e5700880e90c230d16c63044 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Fri, 13 Mar 2026 16:55:32 +0000 Subject: [PATCH 4/6] fix(dotnet): use string type for reasoning effort instead of removed enum The generated RPC types use string? for reasoning effort, not a dedicated enum. Update SetModelAsync to accept string? instead of SessionModelSwitchToRequestReasoningEffort?. Update example to use string literal instead of enum value. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Session.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 1664d072..606c0b05 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -725,10 +725,10 @@ await InvokeRpcAsync( /// /// /// await session.SetModelAsync("gpt-4.1"); - /// await session.SetModelAsync("claude-sonnet-4.6", SessionModelSwitchToRequestReasoningEffort.High); + /// await session.SetModelAsync("claude-sonnet-4.6", "high"); /// /// - public async Task SetModelAsync(string model, SessionModelSwitchToRequestReasoningEffort? reasoningEffort, CancellationToken cancellationToken = default) + public async Task SetModelAsync(string model, string? reasoningEffort, CancellationToken cancellationToken = default) { await Rpc.Model.SwitchToAsync(model, reasoningEffort, cancellationToken); } From 556a9927caa9579dd6dbff578560667b0a6f3fdc Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Fri, 13 Mar 2026 17:07:49 +0000 Subject: [PATCH 5/6] style(nodejs): fix prettier formatting in session.ts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- nodejs/src/session.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/nodejs/src/session.ts b/nodejs/src/session.ts index f449b2ca..67452676 100644 --- a/nodejs/src/session.ts +++ b/nodejs/src/session.ts @@ -727,10 +727,7 @@ export class CopilotSession { * await session.setModel("claude-sonnet-4.6", { reasoningEffort: "high" }); * ``` */ - async setModel( - model: string, - options?: { reasoningEffort?: ReasoningEffort } - ): Promise { + async setModel(model: string, options?: { reasoningEffort?: ReasoningEffort }): Promise { await this.rpc.model.switchTo({ modelId: model, ...options }); } From b834bb5fd323e510636df3e41e4a2493050f3036 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Fri, 13 Mar 2026 17:19:55 +0000 Subject: [PATCH 6/6] test: add e2e tests for setModel with reasoningEffort across all SDKs Add session-level e2e tests that call setModel with reasoningEffort and verify the model_change event includes the reasoning effort value. Update existing skipped RPC-level tests to include reasoningEffort param. Tests added/updated: - Node.js: session.test.ts (e2e), rpc.test.ts (skipped RPC) - Python: test_session.py (e2e), test_rpc.py (skipped RPC) - Go: session_test.go (e2e), rpc_test.go (skipped RPC) - .NET: SessionTests.cs (e2e), RpcTests.cs (skipped RPC) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/test/RpcTests.cs | 4 +-- dotnet/test/SessionTests.cs | 14 +++++++++++ go/internal/e2e/rpc_test.go | 8 +++--- go/internal/e2e/session_test.go | 43 +++++++++++++++++++++++++++++++++ nodejs/test/e2e/rpc.test.ts | 7 ++++-- nodejs/test/e2e/session.test.ts | 12 +++++++++ python/e2e/test_rpc.py | 6 +++-- python/e2e/test_session.py | 22 +++++++++++++++++ 8 files changed, 107 insertions(+), 9 deletions(-) diff --git a/dotnet/test/RpcTests.cs b/dotnet/test/RpcTests.cs index a1369558..e041033b 100644 --- a/dotnet/test/RpcTests.cs +++ b/dotnet/test/RpcTests.cs @@ -72,8 +72,8 @@ public async Task Should_Call_Session_Rpc_Model_SwitchTo() var before = await session.Rpc.Model.GetCurrentAsync(); Assert.NotNull(before.ModelId); - // Switch to a different model - var result = await session.Rpc.Model.SwitchToAsync(modelId: "gpt-4.1"); + // Switch to a different model with reasoning effort + var result = await session.Rpc.Model.SwitchToAsync(modelId: "gpt-4.1", reasoningEffort: "high"); Assert.Equal("gpt-4.1", result.ModelId); // Verify the switch persisted diff --git a/dotnet/test/SessionTests.cs b/dotnet/test/SessionTests.cs index ea9d0da8..8cd4c84e 100644 --- a/dotnet/test/SessionTests.cs +++ b/dotnet/test/SessionTests.cs @@ -440,6 +440,20 @@ public async Task Should_Set_Model_On_Existing_Session() Assert.Equal("gpt-4.1", modelChanged.Data.NewModel); } + [Fact] + public async Task Should_Set_Model_With_ReasoningEffort() + { + var session = await CreateSessionAsync(); + + var modelChangedTask = TestHelper.GetNextEventOfTypeAsync(session); + + await session.SetModelAsync("gpt-4.1", "high"); + + var modelChanged = await modelChangedTask; + Assert.Equal("gpt-4.1", modelChanged.Data.NewModel); + Assert.Equal("high", modelChanged.Data.ReasoningEffort); + } + [Fact] public async Task Should_Log_Messages_At_Various_Levels() { diff --git a/go/internal/e2e/rpc_test.go b/go/internal/e2e/rpc_test.go index 61a5e338..ebcbe113 100644 --- a/go/internal/e2e/rpc_test.go +++ b/go/internal/e2e/rpc_test.go @@ -168,9 +168,11 @@ func TestSessionRpc(t *testing.T) { t.Error("Expected initial modelId to be defined") } - // Switch to a different model + // Switch to a different model with reasoning effort + re := "high" result, err := session.RPC.Model.SwitchTo(t.Context(), &rpc.SessionModelSwitchToParams{ - ModelID: "gpt-4.1", + ModelID: "gpt-4.1", + ReasoningEffort: &re, }) if err != nil { t.Fatalf("Failed to switch model: %v", err) @@ -201,7 +203,7 @@ func TestSessionRpc(t *testing.T) { t.Fatalf("Failed to create session: %v", err) } - if err := session.SetModel(t.Context(), "gpt-4.1"); err != nil { + if err := session.SetModel(t.Context(), "gpt-4.1", copilot.SetModelOptions{ReasoningEffort: "high"}); err != nil { t.Fatalf("SetModel returned error: %v", err) } }) diff --git a/go/internal/e2e/session_test.go b/go/internal/e2e/session_test.go index 4590301d..c3c9cc00 100644 --- a/go/internal/e2e/session_test.go +++ b/go/internal/e2e/session_test.go @@ -895,6 +895,49 @@ func getSystemMessage(exchange testharness.ParsedHttpExchange) string { return "" } +func TestSetModelWithReasoningEffort(t *testing.T) { + ctx := testharness.NewTestContext(t) + client := ctx.NewClient() + t.Cleanup(func() { client.ForceStop() }) + + if err := client.Start(t.Context()); err != nil { + t.Fatalf("Failed to start client: %v", err) + } + + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + }) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + + modelChanged := make(chan copilot.SessionEvent, 1) + session.On(func(event copilot.SessionEvent) { + if event.Type == copilot.SessionModelChange { + select { + case modelChanged <- event: + default: + } + } + }) + + if err := session.SetModel(t.Context(), "gpt-4.1", copilot.SetModelOptions{ReasoningEffort: "high"}); err != nil { + t.Fatalf("SetModel returned error: %v", err) + } + + select { + case evt := <-modelChanged: + if evt.Data.NewModel == nil || *evt.Data.NewModel != "gpt-4.1" { + t.Errorf("Expected newModel 'gpt-4.1', got %v", evt.Data.NewModel) + } + if evt.Data.ReasoningEffort == nil || *evt.Data.ReasoningEffort != "high" { + t.Errorf("Expected reasoningEffort 'high', got %v", evt.Data.ReasoningEffort) + } + case <-time.After(30 * time.Second): + t.Fatal("Timed out waiting for session.model_change event") + } +} + func getToolNames(exchange testharness.ParsedHttpExchange) []string { var names []string for _, tool := range exchange.Request.Tools { diff --git a/nodejs/test/e2e/rpc.test.ts b/nodejs/test/e2e/rpc.test.ts index 62a885d0..d4d732ef 100644 --- a/nodejs/test/e2e/rpc.test.ts +++ b/nodejs/test/e2e/rpc.test.ts @@ -92,8 +92,11 @@ describe("Session RPC", async () => { const before = await session.rpc.model.getCurrent(); expect(before.modelId).toBeDefined(); - // Switch to a different model - const result = await session.rpc.model.switchTo({ modelId: "gpt-4.1" }); + // Switch to a different model with reasoning effort + const result = await session.rpc.model.switchTo({ + modelId: "gpt-4.1", + reasoningEffort: "high", + }); expect(result.modelId).toBe("gpt-4.1"); // Verify the switch persisted diff --git a/nodejs/test/e2e/session.test.ts b/nodejs/test/e2e/session.test.ts index 0ad60edc..1eb8a175 100644 --- a/nodejs/test/e2e/session.test.ts +++ b/nodejs/test/e2e/session.test.ts @@ -461,4 +461,16 @@ describe("Send Blocking Behavior", async () => { session.sendAndWait({ prompt: "Run 'sleep 2 && echo done'" }, 100) ).rejects.toThrow(/Timeout after 100ms/); }); + + it("should set model with reasoningEffort", async () => { + const session = await client.createSession({ onPermissionRequest: approveAll }); + + const modelChangePromise = getNextEventOfType(session, "session.model_change"); + + await session.setModel("gpt-4.1", { reasoningEffort: "high" }); + + const event = await modelChangePromise; + expect(event.data.newModel).toBe("gpt-4.1"); + expect(event.data.reasoningEffort).toBe("high"); + }); }); diff --git a/python/e2e/test_rpc.py b/python/e2e/test_rpc.py index 0db2b4fe..ddf843ba 100644 --- a/python/e2e/test_rpc.py +++ b/python/e2e/test_rpc.py @@ -99,8 +99,10 @@ async def test_should_call_session_rpc_model_switch_to(self, ctx: E2ETestContext before = await session.rpc.model.get_current() assert before.model_id is not None - # Switch to a different model - result = await session.rpc.model.switch_to(SessionModelSwitchToParams(model_id="gpt-4.1")) + # Switch to a different model with reasoning effort + result = await session.rpc.model.switch_to( + SessionModelSwitchToParams(model_id="gpt-4.1", reasoning_effort="high") + ) assert result.model_id == "gpt-4.1" # Verify the switch persisted diff --git a/python/e2e/test_session.py b/python/e2e/test_session.py index a779fd07..9e663fcc 100644 --- a/python/e2e/test_session.py +++ b/python/e2e/test_session.py @@ -558,6 +558,28 @@ def on_event(event): assert by_message["Ephemeral message"].type.value == "session.info" assert by_message["Ephemeral message"].data.info_type == "notification" + async def test_should_set_model_with_reasoning_effort(self, ctx: E2ETestContext): + """Test that setModel passes reasoningEffort and it appears in the model_change event.""" + import asyncio + + session = await ctx.client.create_session( + {"on_permission_request": PermissionHandler.approve_all} + ) + + model_change_event = asyncio.get_event_loop().create_future() + + def on_event(event): + if not model_change_event.done() and event.type.value == "session.model_change": + model_change_event.set_result(event) + + session.on(on_event) + + await session.set_model("gpt-4.1", reasoning_effort="high") + + event = await asyncio.wait_for(model_change_event, timeout=30) + assert event.data.new_model == "gpt-4.1" + assert event.data.reasoning_effort == "high" + def _get_system_message(exchange: dict) -> str: messages = exchange.get("request", {}).get("messages", [])