Skip to content

Commit b7ded42

Browse files
Copilotpatniko
andcommitted
Add reasoningEffort parameter to setModel/SetModel across all SDKs
Co-authored-by: patniko <26906478+patniko@users.noreply.github.com>
1 parent c3031d6 commit b7ded42

13 files changed

Lines changed: 151 additions & 46 deletions

File tree

dotnet/src/Generated/Rpc.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@ internal class SessionModelSwitchToRequest
217217

218218
[JsonPropertyName("modelId")]
219219
public string ModelId { get; set; } = string.Empty;
220+
221+
[JsonPropertyName("reasoningEffort")]
222+
public string? ReasoningEffort { get; set; }
220223
}
221224

222225
public class SessionModeGetResult
@@ -615,9 +618,9 @@ public async Task<SessionModelGetCurrentResult> GetCurrentAsync(CancellationToke
615618
}
616619

617620
/// <summary>Calls "session.model.switchTo".</summary>
618-
public async Task<SessionModelSwitchToResult> SwitchToAsync(string modelId, CancellationToken cancellationToken = default)
621+
public async Task<SessionModelSwitchToResult> SwitchToAsync(string modelId, string? reasoningEffort = null, CancellationToken cancellationToken = default)
619622
{
620-
var request = new SessionModelSwitchToRequest { SessionId = _sessionId, ModelId = modelId };
623+
var request = new SessionModelSwitchToRequest { SessionId = _sessionId, ModelId = modelId, ReasoningEffort = reasoningEffort };
621624
return await CopilotClient.InvokeRpcAsync<SessionModelSwitchToResult>(_rpc, "session.model.switchTo", [request], cancellationToken);
622625
}
623626
}

dotnet/src/Session.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -510,15 +510,17 @@ await InvokeRpcAsync<object>(
510510
/// The new model takes effect for the next message. Conversation history is preserved.
511511
/// </summary>
512512
/// <param name="model">Model ID to switch to (e.g., "gpt-4.1").</param>
513+
/// <param name="reasoningEffort">Optional reasoning effort level for models that support it (e.g., "low", "medium", "high", "xhigh").</param>
513514
/// <param name="cancellationToken">Optional cancellation token.</param>
514515
/// <example>
515516
/// <code>
516517
/// await session.SetModelAsync("gpt-4.1");
518+
/// await session.SetModelAsync("gpt-4.1", reasoningEffort: "high");
517519
/// </code>
518520
/// </example>
519-
public async Task SetModelAsync(string model, CancellationToken cancellationToken = default)
521+
public async Task SetModelAsync(string model, string? reasoningEffort = null, CancellationToken cancellationToken = default)
520522
{
521-
await Rpc.Model.SwitchToAsync(model, cancellationToken);
523+
await Rpc.Model.SwitchToAsync(model, reasoningEffort, cancellationToken);
522524
}
523525

524526
/// <summary>

dotnet/test/SessionTests.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,4 +404,19 @@ public async Task Should_Set_Model_On_Existing_Session()
404404
var modelChanged = await modelChangedTask;
405405
Assert.Equal("gpt-4.1", modelChanged.Data.NewModel);
406406
}
407+
408+
[Fact]
409+
public async Task Should_Set_Model_With_Reasoning_Effort_On_Existing_Session()
410+
{
411+
var session = await CreateSessionAsync();
412+
413+
// Subscribe for the model change event before calling SetModelAsync
414+
var modelChangedTask = TestHelper.GetNextEventOfTypeAsync<SessionModelChangeEvent>(session);
415+
416+
await session.SetModelAsync("gpt-4.1", reasoningEffort: "high");
417+
418+
// Verify a model_change event was emitted with the new model
419+
var modelChanged = await modelChangedTask;
420+
Assert.Equal("gpt-4.1", modelChanged.Data.NewModel);
421+
}
407422
}

go/internal/e2e/rpc_test.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,28 @@ func TestSessionRpc(t *testing.T) {
201201
t.Fatalf("Failed to create session: %v", err)
202202
}
203203

204-
if err := session.SetModel(t.Context(), "gpt-4.1"); err != nil {
204+
if err := session.SetModel(t.Context(), "gpt-4.1", nil); err != nil {
205205
t.Fatalf("SetModel returned error: %v", err)
206206
}
207207
})
208208

209+
t.Run("should call session.SetModel with reasoning effort", func(t *testing.T) {
210+
t.Skip("session.model.switchTo not yet implemented in CLI")
211+
212+
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
213+
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
214+
Model: "claude-sonnet-4.5",
215+
})
216+
if err != nil {
217+
t.Fatalf("Failed to create session: %v", err)
218+
}
219+
220+
effort := "high"
221+
if err := session.SetModel(t.Context(), "gpt-4.1", &effort); err != nil {
222+
t.Fatalf("SetModel with reasoning effort returned error: %v", err)
223+
}
224+
})
225+
209226
t.Run("should get and set session mode", func(t *testing.T) {
210227
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{OnPermissionRequest: copilot.PermissionHandler.ApproveAll})
211228
if err != nil {

go/rpc/generated_rpc.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ type SessionModelSwitchToResult struct {
129129
}
130130

131131
type SessionModelSwitchToParams struct {
132-
ModelID string `json:"modelId"`
132+
ModelID string `json:"modelId"`
133+
ReasoningEffort *string `json:"reasoningEffort,omitempty"`
133134
}
134135

135136
type SessionModeGetResult struct {
@@ -365,6 +366,9 @@ func (a *ModelRpcApi) SwitchTo(ctx context.Context, params *SessionModelSwitchTo
365366
req := map[string]interface{}{"sessionId": a.sessionID}
366367
if params != nil {
367368
req["modelId"] = params.ModelID
369+
if params.ReasoningEffort != nil {
370+
req["reasoningEffort"] = *params.ReasoningEffort
371+
}
368372
}
369373
raw, err := a.client.Request("session.model.switchTo", req)
370374
if err != nil {

go/session.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -579,14 +579,21 @@ func (s *Session) Abort(ctx context.Context) error {
579579

580580
// SetModel changes the model for this session.
581581
// The new model takes effect for the next message. Conversation history is preserved.
582+
// The optional reasoningEffort parameter sets the reasoning effort level for models that support it
583+
// (e.g., "low", "medium", "high", "xhigh"). Pass nil to omit it.
582584
//
583585
// Example:
584586
//
585-
// if err := session.SetModel(context.Background(), "gpt-4.1"); err != nil {
587+
// if err := session.SetModel(context.Background(), "gpt-4.1", nil); err != nil {
586588
// log.Printf("Failed to set model: %v", err)
587589
// }
588-
func (s *Session) SetModel(ctx context.Context, model string) error {
589-
_, err := s.RPC.Model.SwitchTo(ctx, &rpc.SessionModelSwitchToParams{ModelID: model})
590+
//
591+
// effort := "high"
592+
// if err := session.SetModel(context.Background(), "gpt-4.1", &effort); err != nil {
593+
// log.Printf("Failed to set model: %v", err)
594+
// }
595+
func (s *Session) SetModel(ctx context.Context, model string, reasoningEffort *string) error {
596+
_, err := s.RPC.Model.SwitchTo(ctx, &rpc.SessionModelSwitchToParams{ModelID: model, ReasoningEffort: reasoningEffort})
590597
if err != nil {
591598
return fmt.Errorf("failed to set model: %w", err)
592599
}

nodejs/src/generated/rpc.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ export interface SessionModelSwitchToParams {
173173
*/
174174
sessionId: string;
175175
modelId: string;
176+
reasoningEffort?: string;
176177
}
177178

178179
export interface SessionModeGetResult {

nodejs/src/session.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {
1414
PermissionHandler,
1515
PermissionRequest,
1616
PermissionRequestResult,
17+
ReasoningEffort,
1718
SessionEvent,
1819
SessionEventHandler,
1920
SessionEventPayload,
@@ -555,13 +556,15 @@ export class CopilotSession {
555556
* The new model takes effect for the next message. Conversation history is preserved.
556557
*
557558
* @param model - Model ID to switch to
559+
* @param reasoningEffort - Optional reasoning effort level (e.g., "low", "medium", "high", "xhigh")
558560
*
559561
* @example
560562
* ```typescript
561563
* await session.setModel("gpt-4.1");
564+
* await session.setModel("gpt-4.1", "high");
562565
* ```
563566
*/
564-
async setModel(model: string): Promise<void> {
565-
await this.rpc.model.switchTo({ modelId: model });
567+
async setModel(model: string, reasoningEffort?: ReasoningEffort): Promise<void> {
568+
await this.rpc.model.switchTo({ modelId: model, ...(reasoningEffort !== undefined && { reasoningEffort }) });
566569
}
567570
}

nodejs/test/client.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,55 @@ describe("CopilotClient", () => {
113113
spy.mockRestore();
114114
});
115115

116+
it("sends session.model.switchTo RPC with reasoningEffort when provided", async () => {
117+
const client = new CopilotClient();
118+
await client.start();
119+
onTestFinished(() => client.forceStop());
120+
121+
const session = await client.createSession({ onPermissionRequest: approveAll });
122+
123+
// Mock sendRequest to capture the call without hitting the runtime
124+
const spy = vi
125+
.spyOn((client as any).connection!, "sendRequest")
126+
.mockImplementation(async (method: string, _params: any) => {
127+
if (method === "session.model.switchTo") return {};
128+
throw new Error(`Unexpected method: ${method}`);
129+
});
130+
131+
await session.setModel("gpt-4.1", "high");
132+
133+
expect(spy).toHaveBeenCalledWith("session.model.switchTo", {
134+
sessionId: session.sessionId,
135+
modelId: "gpt-4.1",
136+
reasoningEffort: "high",
137+
});
138+
139+
spy.mockRestore();
140+
});
141+
142+
it("sends session.model.switchTo RPC without reasoningEffort when not provided", async () => {
143+
const client = new CopilotClient();
144+
await client.start();
145+
onTestFinished(() => client.forceStop());
146+
147+
const session = await client.createSession({ onPermissionRequest: approveAll });
148+
149+
// Mock sendRequest to capture the call without hitting the runtime
150+
const spy = vi
151+
.spyOn((client as any).connection!, "sendRequest")
152+
.mockImplementation(async (method: string, params: any) => {
153+
if (method === "session.model.switchTo") return {};
154+
throw new Error(`Unexpected method: ${method}`);
155+
});
156+
157+
await session.setModel("gpt-4.1");
158+
159+
const callArgs = spy.mock.calls[0][1] as Record<string, unknown>;
160+
expect(callArgs).not.toHaveProperty("reasoningEffort");
161+
162+
spy.mockRestore();
163+
});
164+
116165
describe("URL parsing", () => {
117166
it("should parse port-only URL format", () => {
118167
const client = new CopilotClient({

python/copilot/generated/rpc.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,16 +468,20 @@ def to_dict(self) -> dict:
468468
@dataclass
469469
class SessionModelSwitchToParams:
470470
model_id: str
471+
reasoning_effort: str | None = None
471472

472473
@staticmethod
473474
def from_dict(obj: Any) -> 'SessionModelSwitchToParams':
474475
assert isinstance(obj, dict)
475476
model_id = from_str(obj.get("modelId"))
476-
return SessionModelSwitchToParams(model_id)
477+
reasoning_effort = from_union([from_str, from_none], obj.get("reasoningEffort"))
478+
return SessionModelSwitchToParams(model_id, reasoning_effort)
477479

478480
def to_dict(self) -> dict:
479481
result: dict = {}
480482
result["modelId"] = from_str(self.model_id)
483+
if self.reasoning_effort is not None:
484+
result["reasoningEffort"] = from_str(self.reasoning_effort)
481485
return result
482486

483487

0 commit comments

Comments
 (0)