+ * When omitted, no agent-specific large output override is sent.
+ *
+ * @param largeOutput
+ * the large output configuration
+ * @return this config for method chaining
+ */
+ public CustomAgentConfig setLargeOutput(LargeToolOutputConfig largeOutput) {
+ this.largeOutput = largeOutput;
+ return this;
+ }
}
diff --git a/java/sdk/src/test/java/com/github/copilot/DataObjectCoverageTest.java b/java/sdk/src/test/java/com/github/copilot/DataObjectCoverageTest.java
index f95c5bcc5..55f5d7b9f 100644
--- a/java/sdk/src/test/java/com/github/copilot/DataObjectCoverageTest.java
+++ b/java/sdk/src/test/java/com/github/copilot/DataObjectCoverageTest.java
@@ -278,6 +278,24 @@ void customAgentConfigReasoningEffortOmittedWhenNull() throws Exception {
assertFalse(json.contains("\"reasoningEffort\""));
}
+ @Test
+ void customAgentConfigLargeOutputSerializationRoundTrip() throws Exception {
+ var mapper = JsonRpcClient.getObjectMapper();
+ var cfg = new CustomAgentConfig().setName("large-output-agent")
+ .setLargeOutput(new LargeToolOutputConfig().setEnabled(false).setMaxSizeBytes(2048L)
+ .setOutputDirectory("/tmp/agent-large-output"));
+
+ var json = mapper.writeValueAsString(cfg);
+ assertTrue(json.contains("\"largeOutput\""));
+ assertTrue(json.contains("\"outputDir\":\"/tmp/agent-large-output\""));
+
+ var deserialized = mapper.readValue(json, CustomAgentConfig.class);
+ assertNotNull(deserialized.getLargeOutput());
+ assertEquals(false, deserialized.getLargeOutput().getEnabled());
+ assertEquals(2048L, deserialized.getLargeOutput().getMaxSizeBytes());
+ assertEquals("/tmp/agent-large-output", deserialized.getLargeOutput().getOutputDirectory());
+ }
+
// ===== PermissionRequestResult setRules =====
@Test
diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts
index 30095186e..c44c7252c 100644
--- a/nodejs/src/client.ts
+++ b/nodejs/src/client.ts
@@ -248,9 +248,12 @@ function toWireMcpServers(
function toWireCustomAgents(agents: CustomAgentConfig[] | undefined): unknown[] | undefined {
if (!agents) return undefined;
return agents.map((agent) => {
- if (!agent.mcpServers) return agent;
- const { mcpServers, ...rest } = agent;
- return { ...rest, mcpServers: toWireMcpServers(mcpServers) };
+ const { mcpServers, largeOutput, ...rest } = agent;
+ return {
+ ...rest,
+ ...(mcpServers ? { mcpServers: toWireMcpServers(mcpServers) } : {}),
+ ...(largeOutput ? { largeOutput: toWireLargeOutput(largeOutput) } : {}),
+ };
});
}
diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts
index 8149c36da..172c0ad43 100644
--- a/nodejs/src/types.ts
+++ b/nodejs/src/types.ts
@@ -1791,6 +1791,11 @@ export interface CustomAgentConfig {
* then inherits the parent effort only if this agent uses the same model.
*/
reasoningEffort?: ReasoningEffort;
+ /**
+ * Large tool output handling for this agent.
+ * When unset, no agent-specific large output override is sent.
+ */
+ largeOutput?: LargeToolOutputConfig;
}
/**
diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts
index 49ec169c4..2271bb152 100644
--- a/nodejs/test/client.test.ts
+++ b/nodejs/test/client.test.ts
@@ -2646,6 +2646,40 @@ describe("CopilotClient", () => {
]);
});
+ it("forwards custom agent large output in session.create request", async () => {
+ const client = new CopilotClient();
+ await client.start();
+ onTestFinished(() => stopClient(client));
+
+ const spy = vi.spyOn((client as any).connection!, "sendRequest");
+ await client.createSession({
+ onPermissionRequest: approveAll,
+ customAgents: [
+ {
+ name: "large-output-agent",
+ prompt: "You are a large output agent.",
+ largeOutput: {
+ enabled: false,
+ maxSizeBytes: 2048,
+ outputDirectory: "/tmp/agent-large-output",
+ },
+ },
+ ],
+ });
+
+ const payload = spy.mock.calls.find((c) => c[0] === "session.create")![1] as any;
+ expect(payload.customAgents).toEqual([
+ expect.objectContaining({
+ name: "large-output-agent",
+ largeOutput: {
+ enabled: false,
+ maxSizeBytes: 2048,
+ outputDir: "/tmp/agent-large-output",
+ },
+ }),
+ ]);
+ });
+
it("forwards agent in session.resume request", async () => {
const client = new CopilotClient();
await client.start();
diff --git a/python/copilot/client.py b/python/copilot/client.py
index 6cdd765c3..c91b23369 100644
--- a/python/copilot/client.py
+++ b/python/copilot/client.py
@@ -4035,6 +4035,8 @@ def _convert_custom_agent_to_wire_format(
wire_agent["model"] = agent["model"]
if "reasoning_effort" in agent:
wire_agent["reasoningEffort"] = agent["reasoning_effort"]
+ if "large_output" in agent:
+ wire_agent["largeOutput"] = _large_output_to_wire(agent["large_output"])
return wire_agent
def _convert_default_agent_to_wire_format(
diff --git a/python/copilot/session.py b/python/copilot/session.py
index 92c24bdd8..0133f70fe 100644
--- a/python/copilot/session.py
+++ b/python/copilot/session.py
@@ -1161,6 +1161,9 @@ class CustomAgentConfig(TypedDict, total=False):
# Reasoning effort for this agent's model. When omitted, the runtime resolves
# model configuration, then inherits the parent effort only for the same model.
reasoning_effort: NotRequired[ReasoningEffort]
+ # Large output handling for this agent. When omitted, no agent-specific
+ # large output override is sent.
+ large_output: NotRequired[LargeToolOutputConfig]
class DefaultAgentConfig(TypedDict, total=False):
diff --git a/python/test_client.py b/python/test_client.py
index cf4bdf192..36126746f 100644
--- a/python/test_client.py
+++ b/python/test_client.py
@@ -9,6 +9,7 @@
import os
from datetime import UTC, datetime
from tempfile import TemporaryDirectory
+from typing import Any
from unittest.mock import AsyncMock, Mock, patch
import pytest
@@ -1166,6 +1167,48 @@ async def mock_request(method, params, **kwargs):
finally:
await client.force_stop()
+ @pytest.mark.asyncio
+ async def test_custom_agent_large_output_uses_wire_output_dir(self) -> None:
+ client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
+ await client.start()
+ try:
+ captured: dict[str, Any] = {}
+
+ async def mock_request(method: str, params: dict[str, Any], **kwargs: Any) -> Any:
+ captured[method] = params
+ if method == "session.create":
+ result = {"sessionId": params.get("sessionId") or "session-1"}
+ callback = kwargs.get("on_response_inline")
+ if callback is not None:
+ callback(result)
+ return result
+ return {}
+
+ client._client.request = mock_request
+
+ await client.create_session(
+ on_permission_request=PermissionHandler.approve_all,
+ custom_agents=[
+ {
+ "name": "large-output-agent",
+ "prompt": "You are a large output agent.",
+ "large_output": {
+ "enabled": False,
+ "max_size_bytes": 2048,
+ "output_directory": "/tmp/agent-large-output",
+ },
+ }
+ ],
+ )
+
+ assert captured["session.create"]["customAgents"][0]["largeOutput"] == {
+ "enabled": False,
+ "maxSizeBytes": 2048,
+ "outputDir": "/tmp/agent-large-output",
+ }
+ finally:
+ await client.force_stop()
+
@pytest.mark.asyncio
async def test_create_and_resume_session_forward_memory(self):
client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH))
diff --git a/rust/src/types.rs b/rust/src/types.rs
index 1946ded08..7330f2f90 100644
--- a/rust/src/types.rs
+++ b/rust/src/types.rs
@@ -668,6 +668,11 @@ pub struct CustomAgentConfig {
/// parent effort only for the same model.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reasoning_effort: Option