+ * When omitted, no per-agent override is sent and the backend chooses its
+ * default. The parent session effort is not inherited.
+ *
+ * @param reasoningEffort
+ * the reasoning effort level
+ * @return this config for method chaining
+ */
+ public CustomAgentConfig setReasoningEffort(String reasoningEffort) {
+ this.reasoningEffort = reasoningEffort;
+ return this;
+ }
}
diff --git a/java/src/test/java/com/github/copilot/DataObjectCoverageTest.java b/java/src/test/java/com/github/copilot/DataObjectCoverageTest.java
index ece824234b..f38a038368 100644
--- a/java/src/test/java/com/github/copilot/DataObjectCoverageTest.java
+++ b/java/src/test/java/com/github/copilot/DataObjectCoverageTest.java
@@ -186,7 +186,7 @@ void postToolUseHookInputSessionIdRoundTrip() {
assertEquals("session-xyz", input.getSessionId());
}
- // ===== CustomAgentConfig model field =====
+ // ===== CustomAgentConfig model fields =====
@Test
void customAgentConfigModelGetterAndSetter() {
@@ -227,6 +227,36 @@ void customAgentConfigModelOmittedWhenNull() throws Exception {
assertFalse(json.contains("\"model\""));
}
+ @Test
+ void customAgentConfigReasoningEffortGetterAndFluentSetter() {
+ var cfg = new CustomAgentConfig();
+ assertNull(cfg.getReasoningEffort());
+
+ var result = cfg.setReasoningEffort("high");
+ assertSame(cfg, result);
+ assertEquals("high", cfg.getReasoningEffort());
+ }
+
+ @Test
+ void customAgentConfigReasoningEffortSerializationRoundTrip() throws Exception {
+ var mapper = JsonRpcClient.getObjectMapper();
+ var cfg = new CustomAgentConfig().setName("reasoning-agent").setReasoningEffort("high");
+
+ var json = mapper.writeValueAsString(cfg);
+ assertTrue(json.contains("\"reasoningEffort\":\"high\""));
+
+ var deserialized = mapper.readValue(json, CustomAgentConfig.class);
+ assertEquals("high", deserialized.getReasoningEffort());
+ }
+
+ @Test
+ void customAgentConfigReasoningEffortOmittedWhenNull() throws Exception {
+ var mapper = JsonRpcClient.getObjectMapper();
+ var json = mapper.writeValueAsString(new CustomAgentConfig().setName("default-agent"));
+
+ assertFalse(json.contains("\"reasoningEffort\""));
+ }
+
// ===== PermissionRequestResult setRules =====
@Test
diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts
index 12ab0860b0..660508ad50 100644
--- a/nodejs/src/types.ts
+++ b/nodejs/src/types.ts
@@ -1632,6 +1632,12 @@ export interface CustomAgentConfig {
* falling back to the parent session model if unavailable.
*/
model?: string;
+ /**
+ * Reasoning effort level for this agent's model.
+ * When omitted, no per-agent override is sent and the backend chooses its
+ * default. The parent session effort is not inherited.
+ */
+ reasoningEffort?: ReasoningEffort;
}
/**
diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts
index 07be2b95e3..695ffe0f07 100644
--- a/nodejs/test/client.test.ts
+++ b/nodejs/test/client.test.ts
@@ -2216,9 +2216,10 @@ describe("CopilotClient", () => {
const payload = spy.mock.calls.find((c) => c[0] === "session.create")![1] as any;
expect(payload.agent).toBe("test-agent");
expect(payload.customAgents).toEqual([expect.objectContaining({ name: "test-agent" })]);
+ expect(payload.customAgents[0].reasoningEffort).toBeUndefined();
});
- it("forwards custom agent model in session.create request", async () => {
+ it("forwards custom agent model and reasoning effort in session.create request", async () => {
const client = new CopilotClient();
await client.start();
onTestFinished(() => stopClient(client));
@@ -2231,13 +2232,18 @@ describe("CopilotClient", () => {
name: "model-agent",
prompt: "You are a model agent.",
model: "claude-haiku-4.5",
+ reasoningEffort: "high",
},
],
});
const payload = spy.mock.calls.find((c) => c[0] === "session.create")![1] as any;
expect(payload.customAgents).toEqual([
- expect.objectContaining({ name: "model-agent", model: "claude-haiku-4.5" }),
+ expect.objectContaining({
+ name: "model-agent",
+ model: "claude-haiku-4.5",
+ reasoningEffort: "high",
+ }),
]);
});
diff --git a/python/copilot/client.py b/python/copilot/client.py
index 94eca4f89c..f2f7e3cb35 100644
--- a/python/copilot/client.py
+++ b/python/copilot/client.py
@@ -3736,6 +3736,8 @@ def _convert_custom_agent_to_wire_format(
wire_agent["skills"] = agent["skills"]
if "model" in agent:
wire_agent["model"] = agent["model"]
+ if "reasoning_effort" in agent:
+ wire_agent["reasoningEffort"] = agent["reasoning_effort"]
return wire_agent
def _convert_default_agent_to_wire_format(
diff --git a/python/copilot/session.py b/python/copilot/session.py
index d0f402ef79..b6736939ac 100644
--- a/python/copilot/session.py
+++ b/python/copilot/session.py
@@ -1080,6 +1080,9 @@ class CustomAgentConfig(TypedDict, total=False):
skills: NotRequired[list[str]]
# Model identifier (e.g. "claude-haiku-4.5"); runtime falls back to parent model if unavailable
model: NotRequired[str]
+ # Reasoning effort for this agent's model. When omitted, no per-agent override
+ # is sent and the backend chooses its default; the parent effort is not inherited.
+ reasoning_effort: NotRequired[ReasoningEffort]
class DefaultAgentConfig(TypedDict, total=False):
diff --git a/python/test_client.py b/python/test_client.py
index 9cb00d809f..e1d8d9522a 100644
--- a/python/test_client.py
+++ b/python/test_client.py
@@ -2245,6 +2245,32 @@ def test_model_field_is_omitted_when_absent(self):
wire = client._convert_custom_agent_to_wire_format(agent)
assert "model" not in wire
+ def test_reasoning_effort_is_forwarded_in_camel_case(self):
+ from copilot.client import CopilotClient
+ from copilot.session import CustomAgentConfig
+
+ client = CopilotClient.__new__(CopilotClient)
+ agent: CustomAgentConfig = {
+ "name": "reasoning-agent",
+ "prompt": "Think carefully.",
+ "reasoning_effort": "high",
+ }
+ wire = client._convert_custom_agent_to_wire_format(agent)
+ assert wire["reasoningEffort"] == "high"
+ assert "reasoning_effort" not in wire
+
+ def test_reasoning_effort_is_omitted_when_absent(self):
+ from copilot.client import CopilotClient
+ from copilot.session import CustomAgentConfig
+
+ client = CopilotClient.__new__(CopilotClient)
+ agent: CustomAgentConfig = {
+ "name": "default-agent",
+ "prompt": "Use runtime defaults.",
+ }
+ wire = client._convert_custom_agent_to_wire_format(agent)
+ assert "reasoningEffort" not in wire
+
class TestPostToolUseFailureHookDispatch:
"""Unit tests for the postToolUseFailure handler dispatch."""
diff --git a/rust/src/types.rs b/rust/src/types.rs
index 81eac81ca2..7907b0c7f2 100644
--- a/rust/src/types.rs
+++ b/rust/src/types.rs
@@ -628,6 +628,12 @@ pub struct CustomAgentConfig {
/// falling back to the parent session model if unavailable.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model: Option