From f02ae8a7f06fe4078b2f1e1b2a50419c4957865c Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Mon, 13 Apr 2026 13:58:33 -0700 Subject: [PATCH 1/4] feat: add per-agent tool visibility via defaultAgent.excludedTools Add a new DefaultAgentConfig type and defaultAgent property to SessionConfig across all four SDKs (Node.js, Python, Go, .NET). This allows tools to be hidden from the default agent while remaining available to custom sub-agents, enabling the orchestrator pattern where the default agent delegates heavy-context work to specialized sub-agents. The default agent is the built-in agent that handles turns when no custom agent is selected. Tools listed in defaultAgent.excludedTools are excluded from the default agent but remain available to sub-agents that reference them in their tools array. Changes: - Node.js: DefaultAgentConfig interface, defaultAgent on SessionConfig/ ResumeSessionConfig, RPC pass-through, 2 unit tests - Python: DefaultAgentConfig TypedDict, default_agent parameter on create_session()/resume_session(), wire format conversion - Go: DefaultAgentConfig struct, DefaultAgent on config and request structs - .NET: DefaultAgentConfig class, DefaultAgent property on configs, copy constructors, and RPC request records - Docs: Agent-Exclusive Tools section in custom-agents.md - Test scenario: custom-agents scenario updated with defaultAgent usage Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/custom-agents.md | 147 ++++++++++++++++++ dotnet/src/Client.cs | 4 + dotnet/src/Types.cs | 31 ++++ dotnet/test/CloneTests.cs | 3 + dotnet/test/SessionTests.cs | 29 ++++ go/client.go | 2 + go/internal/e2e/session_test.go | 51 ++++++ go/types.go | 16 ++ nodejs/src/client.ts | 2 + nodejs/src/index.ts | 1 + nodejs/src/types.ts | 24 +++ nodejs/test/client.test.ts | 39 +++++ nodejs/test/e2e/mcp_and_agents.test.ts | 82 +++++++++- python/copilot/client.py | 32 ++++ python/copilot/session.py | 18 +++ python/e2e/test_session.py | 28 ++++ test/scenarios/tools/custom-agents/README.md | 18 ++- .../tools/custom-agents/csharp/Program.cs | 17 +- test/scenarios/tools/custom-agents/go/main.go | 17 +- .../tools/custom-agents/python/main.py | 38 +++-- .../custom-agents/typescript/src/index.ts | 18 ++- ...agent_configuration_on_session_resume.yaml | 14 ++ ...ide_excluded_tools_from_default_agent.yaml | 10 ++ ...ssion_with_defaultagent_excludedtools.yaml | 10 ++ 24 files changed, 626 insertions(+), 25 deletions(-) create mode 100644 test/snapshots/mcp_and_agents/should_accept_defaultagent_configuration_on_session_resume.yaml create mode 100644 test/snapshots/mcp_and_agents/should_hide_excluded_tools_from_default_agent.yaml create mode 100644 test/snapshots/session/should_create_a_session_with_defaultagent_excludedtools.yaml diff --git a/docs/features/custom-agents.md b/docs/features/custom-agents.md index f3c508922..e8c73a5cd 100644 --- a/docs/features/custom-agents.md +++ b/docs/features/custom-agents.md @@ -759,6 +759,153 @@ const session = await client.createSession({ > **Note:** When `tools` is `null` or omitted, the agent inherits access to all tools configured on the session. Use explicit tool lists to enforce the principle of least privilege. +## Agent-Exclusive Tools + +Use the `defaultAgent` property on the session configuration to hide specific tools from the default agent (the built-in agent that handles turns when no custom agent is selected). This forces the main agent to delegate to sub-agents when those tools' capabilities are needed, keeping the main agent's context clean. + +This is useful when: +- Certain tools generate large amounts of context that would overwhelm the main agent +- You want the main agent to act as an orchestrator, delegating heavy work to specialized sub-agents +- You need strict separation between orchestration and execution + +
+Node.js / TypeScript + +```typescript +import { CopilotClient, defineTool, approveAll } from "@github/copilot-sdk"; +import { z } from "zod"; + +const heavyContextTool = defineTool({ + name: "analyze-codebase", + description: "Performs deep analysis of the codebase, generating extensive context", + parameters: z.object({ query: z.string() }), + handler: async ({ query }) => { + // ... expensive analysis that returns lots of data + return { analysis: "..." }; + }, +}); + +const session = await client.createSession({ + tools: [heavyContextTool], + defaultAgent: { + excludedTools: ["analyze-codebase"], + }, + customAgents: [ + { + name: "researcher", + description: "Deep codebase analysis agent with access to heavy-context tools", + tools: ["analyze-codebase"], + prompt: "You perform thorough codebase analysis using the analyze-codebase tool.", + }, + ], +}); +``` + +
+ +
+Python + +```python +from copilot import CopilotClient +from copilot.tools import Tool + +heavy_tool = Tool( + name="analyze-codebase", + description="Performs deep analysis of the codebase", + handler=analyze_handler, + parameters={"type": "object", "properties": {"query": {"type": "string"}}}, +) + +session = await client.create_session( + tools=[heavy_tool], + default_agent={"excluded_tools": ["analyze-codebase"]}, + custom_agents=[ + { + "name": "researcher", + "description": "Deep codebase analysis agent", + "tools": ["analyze-codebase"], + "prompt": "You perform thorough codebase analysis.", + }, + ], + on_permission_request=approve_all, +) +``` + +
+ +
+Go + +```go +session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + Tools: []copilot.Tool{heavyTool}, + defaultAgent: &copilot.DefaultAgentConfig{ + ExcludedTools: []string{"analyze-codebase"}, + }, + CustomAgents: []copilot.CustomAgentConfig{ + { + Name: "researcher", + Description: "Deep codebase analysis agent", + Tools: []string{"analyze-codebase"}, + Prompt: "You perform thorough codebase analysis.", + }, + }, +}) +``` + +
+ +
+C# / .NET + +```csharp +var session = await client.CreateSessionAsync(new SessionConfig +{ + Tools = [analyzeCodebaseTool], + defaultAgent = new DefaultAgentConfig + { + ExcludedTools = ["analyze-codebase"], + }, + CustomAgents = + [ + new CustomAgentConfig + { + Name = "researcher", + Description = "Deep codebase analysis agent", + Tools = ["analyze-codebase"], + Prompt = "You perform thorough codebase analysis.", + }, + ], +}); +``` + +
+ +### How It Works + +Tools listed in `defaultAgent.excludedTools`: + +1. **Are registered** — their handlers are available for execution +2. **Are hidden** from the main agent's tool list — the LLM won't see or call them directly +3. **Remain available** to any custom sub-agent that includes them in its `tools` array + +### Interaction with Other Tool Filters + +`defaultAgent.excludedTools` is orthogonal to the session-level `availableTools` and `excludedTools`: + +| Filter | Scope | Effect | +|--------|-------|--------| +| `availableTools` | Session-wide | Allowlist — only these tools exist for anyone | +| `excludedTools` | Session-wide | Blocklist — these tools are blocked for everyone | +| `defaultAgent.excludedTools` | Main agent only | These tools are hidden from the main agent but available to sub-agents | + +Precedence: +1. Session-level `availableTools`/`excludedTools` are applied first (globally) +2. `defaultAgent.excludedTools` is applied on top, further restricting the main agent only + +> **Note:** If a tool is in both `excludedTools` (session-level) and `defaultAgent.excludedTools`, the session-level exclusion takes precedence — the tool is unavailable to everyone. + ## Attaching MCP Servers to Agents Each custom agent can have its own MCP (Model Context Protocol) servers, giving it access to specialized data sources: diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 0124008f4..920054bc1 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -500,6 +500,7 @@ public async Task CreateSessionAsync(SessionConfig config, Cance config.McpServers, "direct", config.CustomAgents, + config.DefaultAgent, config.Agent, config.ConfigDir, config.EnableConfigDiscovery, @@ -625,6 +626,7 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes config.McpServers, "direct", config.CustomAgents, + config.DefaultAgent, config.Agent, config.SkillDirectories, config.DisabledSkills, @@ -1639,6 +1641,7 @@ internal record CreateSessionRequest( IDictionary? McpServers, string? EnvValueMode, IList? CustomAgents, + DefaultAgentConfig? DefaultAgent, string? Agent, string? ConfigDir, bool? EnableConfigDiscovery, @@ -1694,6 +1697,7 @@ internal record ResumeSessionRequest( IDictionary? McpServers, string? EnvValueMode, IList? CustomAgents, + DefaultAgentConfig? DefaultAgent, string? Agent, IList? SkillDirectories, IList? DisabledSkills, diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index 1fd8afa39..d4570f9fc 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -1656,6 +1656,21 @@ public class CustomAgentConfig public IList? Skills { get; set; } } +/// +/// Configuration for the default agent (the built-in agent that handles turns when no custom agent is selected). +/// Use to hide specific tools from the default agent +/// while keeping them available to custom sub-agents. +/// +public class DefaultAgentConfig +{ + /// + /// List of tool names to exclude from the default agent. + /// These tools remain available to custom sub-agents that reference them + /// in their list. + /// + public IList? ExcludedTools { get; set; } +} + /// /// Configuration for infinite sessions with automatic context compaction and workspace persistence. /// When enabled, sessions automatically manage context window limits through background compaction @@ -1709,6 +1724,7 @@ protected SessionConfig(SessionConfig? other) Commands = other.Commands is not null ? [.. other.Commands] : null; ConfigDir = other.ConfigDir; CustomAgents = other.CustomAgents is not null ? [.. other.CustomAgents] : null; + DefaultAgent = other.DefaultAgent; Agent = other.Agent; DisabledSkills = other.DisabledSkills is not null ? [.. other.DisabledSkills] : null; EnableConfigDiscovery = other.EnableConfigDiscovery; @@ -1859,6 +1875,13 @@ protected SessionConfig(SessionConfig? other) /// public IList? CustomAgents { get; set; } + /// + /// Configuration for the default agent (the built-in agent that handles turns when no custom agent is selected). + /// Use to hide specific tools from the default agent + /// while keeping them available to custom sub-agents. + /// + public DefaultAgentConfig? DefaultAgent { get; set; } + /// /// Name of the custom agent to activate when the session starts. /// Must match the of one of the agents in . @@ -1938,6 +1961,7 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) Commands = other.Commands is not null ? [.. other.Commands] : null; ConfigDir = other.ConfigDir; CustomAgents = other.CustomAgents is not null ? [.. other.CustomAgents] : null; + DefaultAgent = other.DefaultAgent; Agent = other.Agent; DisabledSkills = other.DisabledSkills is not null ? [.. other.DisabledSkills] : null; DisableResume = other.DisableResume; @@ -2093,6 +2117,13 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) /// public IList? CustomAgents { get; set; } + /// + /// Configuration for the default agent (the built-in agent that handles turns when no custom agent is selected). + /// Use to hide specific tools from the default agent + /// while keeping them available to custom sub-agents. + /// + public DefaultAgentConfig? DefaultAgent { get; set; } + /// /// Name of the custom agent to activate when the session starts. /// Must match the of one of the agents in . diff --git a/dotnet/test/CloneTests.cs b/dotnet/test/CloneTests.cs index 39c42fb25..8fd80f868 100644 --- a/dotnet/test/CloneTests.cs +++ b/dotnet/test/CloneTests.cs @@ -89,6 +89,7 @@ public void SessionConfig_Clone_CopiesAllProperties() McpServers = new Dictionary { ["server1"] = new McpStdioServerConfig { Command = "echo" } }, CustomAgents = [new CustomAgentConfig { Name = "agent1" }], Agent = "agent1", + DefaultAgent = new DefaultAgentConfig { ExcludedTools = ["hidden-tool"] }, SkillDirectories = ["/skills"], DisabledSkills = ["skill1"], }; @@ -107,6 +108,7 @@ public void SessionConfig_Clone_CopiesAllProperties() Assert.Equal(original.McpServers.Count, clone.McpServers!.Count); Assert.Equal(original.CustomAgents.Count, clone.CustomAgents!.Count); Assert.Equal(original.Agent, clone.Agent); + Assert.Equal(original.DefaultAgent!.ExcludedTools, clone.DefaultAgent!.ExcludedTools); Assert.Equal(original.SkillDirectories, clone.SkillDirectories); Assert.Equal(original.DisabledSkills, clone.DisabledSkills); } @@ -243,6 +245,7 @@ public void Clone_WithNullCollections_ReturnsNullCollections() Assert.Null(clone.SkillDirectories); Assert.Null(clone.DisabledSkills); Assert.Null(clone.Tools); + Assert.Null(clone.DefaultAgent); } [Fact] diff --git a/dotnet/test/SessionTests.cs b/dotnet/test/SessionTests.cs index 59c11a84f..241698516 100644 --- a/dotnet/test/SessionTests.cs +++ b/dotnet/test/SessionTests.cs @@ -162,6 +162,35 @@ public async Task Should_Create_A_Session_With_ExcludedTools() Assert.Contains("grep", toolNames); } + [Fact] + public async Task Should_Create_A_Session_With_DefaultAgent_ExcludedTools() + { + var session = await CreateSessionAsync(new SessionConfig + { + Tools = + [ + AIFunctionFactory.Create( + (string input) => "SECRET", + "secret_tool", + "A secret tool hidden from the default agent"), + ], + DefaultAgent = new DefaultAgentConfig + { + ExcludedTools = ["secret_tool"], + }, + }); + + await session.SendAsync(new MessageOptions { Prompt = "What is 1+1?" }); + await TestHelper.GetFinalAssistantMessageAsync(session); + + // The real assertion: verify the runtime excluded the tool from the CAPI request + var traffic = await Ctx.GetExchangesAsync(); + Assert.NotEmpty(traffic); + + var toolNames = GetToolNames(traffic[0]); + Assert.DoesNotContain("secret_tool", toolNames); + } + [Fact] public async Task Should_Create_Session_With_Custom_Tool() { diff --git a/go/client.go b/go/client.go index db8438041..1ac108514 100644 --- a/go/client.go +++ b/go/client.go @@ -592,6 +592,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses req.MCPServers = config.MCPServers req.EnvValueMode = "direct" req.CustomAgents = config.CustomAgents + req.DefaultAgent = config.DefaultAgent req.Agent = config.Agent req.SkillDirectories = config.SkillDirectories req.DisabledSkills = config.DisabledSkills @@ -766,6 +767,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, req.MCPServers = config.MCPServers req.EnvValueMode = "direct" req.CustomAgents = config.CustomAgents + req.DefaultAgent = config.DefaultAgent req.Agent = config.Agent req.SkillDirectories = config.SkillDirectories req.DisabledSkills = config.DisabledSkills diff --git a/go/internal/e2e/session_test.go b/go/internal/e2e/session_test.go index 1fed130d3..96ab7a908 100644 --- a/go/internal/e2e/session_test.go +++ b/go/internal/e2e/session_test.go @@ -313,6 +313,57 @@ func TestSession(t *testing.T) { } }) + t.Run("should create a session with defaultAgent excludedTools", func(t *testing.T) { + ctx.ConfigureForTest(t) + + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + Tools: []copilot.Tool{ + { + Name: "secret_tool", + Description: "A secret tool hidden from the default agent", + Parameters: map[string]any{ + "type": "object", + "properties": map[string]any{"input": map[string]any{"type": "string"}}, + }, + Handler: func(invocation copilot.ToolInvocation) (copilot.ToolResult, error) { + return copilot.ToolResult{TextResultForLLM: "SECRET", ResultType: "success"}, nil + }, + }, + }, + DefaultAgent: &copilot.DefaultAgentConfig{ + ExcludedTools: []string{"secret_tool"}, + }, + }) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + + _, err = session.Send(t.Context(), copilot.MessageOptions{Prompt: "What is 1+1?"}) + if err != nil { + t.Fatalf("Failed to send message: %v", err) + } + + _, err = testharness.GetFinalAssistantMessage(t.Context(), session) + if err != nil { + t.Fatalf("Failed to get assistant message: %v", err) + } + + // The real assertion: verify the runtime excluded the tool from the CAPI request + traffic, err := ctx.GetExchanges() + if err != nil { + t.Fatalf("Failed to get exchanges: %v", err) + } + if len(traffic) == 0 { + t.Fatal("Expected at least one exchange") + } + + toolNames := getToolNames(traffic[0]) + if contains(toolNames, "secret_tool") { + t.Errorf("Expected 'secret_tool' to be excluded from default agent, got %v", toolNames) + } + }) + t.Run("should create session with custom tool", func(t *testing.T) { ctx.ConfigureForTest(t) diff --git a/go/types.go b/go/types.go index f889d3e2a..66bba3c5f 100644 --- a/go/types.go +++ b/go/types.go @@ -454,6 +454,15 @@ type CustomAgentConfig struct { Skills []string `json:"skills,omitempty"` } +// DefaultAgentConfig configures the default agent (the built-in agent that handles turns when no custom agent is selected). +// Use ExcludedTools to hide specific tools from the default agent while keeping +// them available to custom sub-agents. +type DefaultAgentConfig struct { + // ExcludedTools is a list of tool names to exclude from the default agent. + // These tools remain available to custom sub-agents that reference them in their Tools list. + ExcludedTools []string `json:"excludedTools,omitempty"` +} + // InfiniteSessionConfig configures infinite sessions with automatic context compaction // and workspace persistence. When enabled, sessions automatically manage context window // limits through background compaction and persist state to a workspace directory. @@ -536,6 +545,9 @@ type SessionConfig struct { MCPServers map[string]MCPServerConfig // CustomAgents configures custom agents for the session CustomAgents []CustomAgentConfig + // DefaultAgent configures the default agent (the built-in agent that handles turns when no custom agent is selected). + // Use ExcludedTools to hide tools from the default agent while keeping them available to sub-agents. + DefaultAgent *DefaultAgentConfig // Agent is the name of the custom agent to activate when the session starts. // Must match the Name of one of the agents in CustomAgents. Agent string @@ -744,6 +756,8 @@ type ResumeSessionConfig struct { MCPServers map[string]MCPServerConfig // CustomAgents configures custom agents for the session CustomAgents []CustomAgentConfig + // DefaultAgent configures the default agent (the built-in agent that handles turns when no custom agent is selected). + DefaultAgent *DefaultAgentConfig // Agent is the name of the custom agent to activate when the session starts. // Must match the Name of one of the agents in CustomAgents. Agent string @@ -955,6 +969,7 @@ type createSessionRequest struct { MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"` EnvValueMode string `json:"envValueMode,omitempty"` CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"` + DefaultAgent *DefaultAgentConfig `json:"defaultAgent,omitempty"` Agent string `json:"agent,omitempty"` ConfigDir string `json:"configDir,omitempty"` EnableConfigDiscovery *bool `json:"enableConfigDiscovery,omitempty"` @@ -1003,6 +1018,7 @@ type resumeSessionRequest struct { MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"` EnvValueMode string `json:"envValueMode,omitempty"` CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"` + DefaultAgent *DefaultAgentConfig `json:"defaultAgent,omitempty"` Agent string `json:"agent,omitempty"` SkillDirectories []string `json:"skillDirectories,omitempty"` DisabledSkills []string `json:"disabledSkills,omitempty"` diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index c5b84a6d4..024e65d6b 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -751,6 +751,7 @@ export class CopilotClient { mcpServers: config.mcpServers, envValueMode: "direct", customAgents: config.customAgents, + defaultAgent: config.defaultAgent, agent: config.agent, configDir: config.configDir, enableConfigDiscovery: config.enableConfigDiscovery, @@ -891,6 +892,7 @@ export class CopilotClient { mcpServers: config.mcpServers, envValueMode: "direct", customAgents: config.customAgents, + defaultAgent: config.defaultAgent, agent: config.agent, skillDirectories: config.skillDirectories, disabledSkills: config.disabledSkills, diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts index e2942998a..503d0942d 100644 --- a/nodejs/src/index.ts +++ b/nodejs/src/index.ts @@ -38,6 +38,7 @@ export type { MCPStdioServerConfig, MCPHTTPServerConfig, MCPServerConfig, + DefaultAgentConfig, MessageOptions, ModelBilling, ModelCapabilities, diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 0c901f989..04c9024ab 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -1114,6 +1114,21 @@ export interface CustomAgentConfig { skills?: string[]; } +/** + * Configuration for the default agent (the built-in agent that handles + * turns when no custom agent is selected). + * Use this to control tool visibility for the default agent independently of custom sub-agents. + */ +export interface DefaultAgentConfig { + /** + * List of tool names to exclude from the default agent. + * These tools remain available to custom sub-agents that reference them in their `tools` array. + * Use this to register tools that should only be accessed via delegation to sub-agents, + * keeping the default agent's context clean. + */ + excludedTools?: string[]; +} + /** * Configuration for infinite sessions with automatic context compaction and workspace persistence. * When enabled, sessions automatically manage context window limits through background compaction @@ -1281,6 +1296,14 @@ export interface SessionConfig { */ customAgents?: CustomAgentConfig[]; + /** + * Configuration for the default agent (the built-in agent that handles + * turns when no custom agent is selected). + * Use `excludedTools` to hide specific tools from the default agent while keeping + * them available to custom sub-agents. + */ + defaultAgent?: DefaultAgentConfig; + /** * Name of the custom agent to activate when the session starts. * Must match the `name` of one of the agents in `customAgents`. @@ -1348,6 +1371,7 @@ export type ResumeSessionConfig = Pick< | "enableConfigDiscovery" | "mcpServers" | "customAgents" + | "defaultAgent" | "agent" | "skillDirectories" | "disabledSkills" diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts index 870ccb1ed..e3ad3d879 100644 --- a/nodejs/test/client.test.ts +++ b/nodejs/test/client.test.ts @@ -159,6 +159,45 @@ describe("CopilotClient", () => { spy.mockRestore(); }); + it("forwards defaultAgent in session.create request", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const spy = vi.spyOn((client as any).connection!, "sendRequest"); + await client.createSession({ + defaultAgent: { excludedTools: ["heavy-tool"] }, + onPermissionRequest: approveAll, + }); + + expect(spy).toHaveBeenCalledWith( + "session.create", + expect.objectContaining({ + defaultAgent: { excludedTools: ["heavy-tool"] }, + }) + ); + }); + + it("forwards defaultAgent in session.resume request", 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"); + await client.resumeSession(session.sessionId, { + defaultAgent: { excludedTools: ["heavy-tool"] }, + onPermissionRequest: approveAll, + }); + + expect(spy).toHaveBeenCalledWith( + "session.resume", + expect.objectContaining({ + defaultAgent: { excludedTools: ["heavy-tool"] }, + }) + ); + }); + it("does not request permissions on session.resume when using the default joinSession handler", async () => { const client = new CopilotClient(); await client.start(); diff --git a/nodejs/test/e2e/mcp_and_agents.test.ts b/nodejs/test/e2e/mcp_and_agents.test.ts index 59e6d498b..45d505136 100644 --- a/nodejs/test/e2e/mcp_and_agents.test.ts +++ b/nodejs/test/e2e/mcp_and_agents.test.ts @@ -5,8 +5,14 @@ import { dirname, resolve } from "path"; import { fileURLToPath } from "url"; import { describe, expect, it } from "vitest"; -import type { CustomAgentConfig, MCPStdioServerConfig, MCPServerConfig } from "../../src/index.js"; -import { approveAll } from "../../src/index.js"; +import { z } from "zod"; +import type { + CustomAgentConfig, + DefaultAgentConfig, + MCPStdioServerConfig, + MCPServerConfig, +} from "../../src/index.js"; +import { approveAll, defineTool } from "../../src/index.js"; import { createSdkTestContext } from "./harness/sdkTestContext.js"; const __filename = fileURLToPath(import.meta.url); @@ -14,7 +20,7 @@ const __dirname = dirname(__filename); const TEST_MCP_SERVER = resolve(__dirname, "../../../test/harness/test-mcp-server.mjs"); describe("MCP Servers and Custom Agents", async () => { - const { copilotClient: client } = await createSdkTestContext(); + const { copilotClient: client, openAiEndpoint } = await createSdkTestContext(); describe("MCP Servers", () => { it("should accept MCP server configuration on session create", async () => { @@ -296,4 +302,74 @@ describe("MCP Servers and Custom Agents", async () => { await session.disconnect(); }); }); + + describe("Default Agent Tool Exclusion", () => { + it("should hide excluded tools from default agent", async () => { + const secretTool = defineTool("secret_tool", { + description: "A secret tool hidden from the default agent", + parameters: z.object({ + input: z.string().describe("Input to process"), + }), + handler: ({ input }) => `SECRET:${input}`, + }); + + const session = await client.createSession({ + onPermissionRequest: approveAll, + tools: [secretTool], + defaultAgent: { + excludedTools: ["secret_tool"], + }, + }); + + // Ask about the tool — the default agent should not see it + const message = await session.sendAndWait({ + prompt: "Do you have access to a tool called secret_tool? Answer yes or no.", + }); + + // Sanity-check the replayed response (not the actual exclusion assertion) + expect(message?.data.content?.toLowerCase()).toContain("no"); + + // The real assertion: verify the runtime excluded the tool from the CAPI request + const exchanges = await openAiEndpoint.getExchanges(); + const toolNames = exchanges.flatMap((e) => + (e.request.tools ?? []).map((t) => + "function" in t ? t.function.name : "", + ), + ); + expect(toolNames).not.toContain("secret_tool"); + + await session.disconnect(); + }); + + it("should accept defaultAgent configuration on session resume", async () => { + const session1 = await client.createSession({ onPermissionRequest: approveAll }); + const sessionId = session1.sessionId; + await session1.sendAndWait({ prompt: "What is 3+3?" }); + + const secretTool = defineTool("secret_tool", { + description: "A secret tool hidden from the default agent", + parameters: z.object({ + input: z.string().describe("Input to process"), + }), + handler: ({ input }) => `SECRET:${input}`, + }); + + const session2 = await client.resumeSession(sessionId, { + onPermissionRequest: approveAll, + tools: [secretTool], + defaultAgent: { + excludedTools: ["secret_tool"], + }, + }); + + expect(session2.sessionId).toBe(sessionId); + + const message = await session2.sendAndWait({ + prompt: "What is 4+4?", + }); + expect(message?.data.content).toContain("8"); + + await session2.disconnect(); + }); + }); }); diff --git a/python/copilot/client.py b/python/copilot/client.py index 5d62db301..c40274457 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -47,6 +47,7 @@ CopilotSession, CreateSessionFsHandler, CustomAgentConfig, + DefaultAgentConfig, ElicitationHandler, InfiniteSessionConfig, MCPServerConfig, @@ -1198,6 +1199,7 @@ async def create_session( streaming: bool | None = None, mcp_servers: dict[str, MCPServerConfig] | None = None, custom_agents: list[CustomAgentConfig] | None = None, + default_agent: DefaultAgentConfig | dict[str, Any] | None = None, agent: str | None = None, config_dir: str | None = None, enable_config_discovery: bool | None = None, @@ -1235,6 +1237,8 @@ async def create_session( streaming: Whether to enable streaming responses. mcp_servers: MCP server configurations. custom_agents: Custom agent configurations. + default_agent: Configuration for the default agent, + including tool visibility controls. agent: Agent to use for the session. config_dir: Override for the configuration directory. enable_config_discovery: When True, automatically discovers MCP server @@ -1360,6 +1364,10 @@ async def create_session( self._convert_custom_agent_to_wire_format(agent) for agent in custom_agents ] + # Add default agent configuration if provided + if default_agent: + payload["defaultAgent"] = self._convert_default_agent_to_wire_format(default_agent) + # Add agent selection if provided if agent: payload["agent"] = agent @@ -1463,6 +1471,7 @@ async def resume_session( streaming: bool | None = None, mcp_servers: dict[str, MCPServerConfig] | None = None, custom_agents: list[CustomAgentConfig] | None = None, + default_agent: DefaultAgentConfig | dict[str, Any] | None = None, agent: str | None = None, config_dir: str | None = None, enable_config_discovery: bool | None = None, @@ -1500,6 +1509,8 @@ async def resume_session( streaming: Whether to enable streaming responses. mcp_servers: MCP server configurations. custom_agents: Custom agent configurations. + default_agent: Configuration for the default agent, + including tool visibility controls. agent: Agent to use for the session. config_dir: Override for the configuration directory. enable_config_discovery: When True, automatically discovers MCP server @@ -1619,6 +1630,10 @@ async def resume_session( self._convert_custom_agent_to_wire_format(a) for a in custom_agents ] + # Add default agent configuration if provided + if default_agent: + payload["defaultAgent"] = self._convert_default_agent_to_wire_format(default_agent) + if agent: payload["agent"] = agent if skill_directories: @@ -2162,6 +2177,23 @@ def _convert_custom_agent_to_wire_format( wire_agent["skills"] = agent["skills"] return wire_agent + def _convert_default_agent_to_wire_format( + self, config: DefaultAgentConfig | dict[str, Any] + ) -> dict[str, Any]: + """ + Convert default agent config from snake_case to camelCase wire format. + + Args: + config: The default agent configuration in snake_case format. + + Returns: + The default agent configuration in camelCase wire format. + """ + wire: dict[str, Any] = {} + if "excluded_tools" in config: + wire["excludedTools"] = config["excluded_tools"] + return wire + async def _start_cli_server(self) -> None: """ Start the CLI server process. diff --git a/python/copilot/session.py b/python/copilot/session.py index 9552f75b6..0ee6acb90 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -781,6 +781,18 @@ class CustomAgentConfig(TypedDict, total=False): skills: NotRequired[list[str]] +class DefaultAgentConfig(TypedDict, total=False): + """Configuration for the default agent. + + The default agent is the built-in agent that handles turns + when no custom agent is selected. + """ + + # List of tool names to exclude from the default agent. + # These tools remain available to custom sub-agents that reference them. + excluded_tools: list[str] + + class InfiniteSessionConfig(TypedDict, total=False): """ Configuration for infinite sessions with automatic context compaction @@ -863,6 +875,10 @@ class SessionConfig(TypedDict, total=False): mcp_servers: dict[str, MCPServerConfig] # Custom agent configurations for the session custom_agents: list[CustomAgentConfig] + # Configuration for the default agent. + # Use excluded_tools to hide tools from the default agent + # while keeping them available to sub-agents. + default_agent: DefaultAgentConfig # Name of the custom agent to activate when the session starts. # Must match the name of one of the agents in custom_agents. agent: str @@ -924,6 +940,8 @@ class ResumeSessionConfig(TypedDict, total=False): mcp_servers: dict[str, MCPServerConfig] # Custom agent configurations for the session custom_agents: list[CustomAgentConfig] + # Configuration for the default agent. + default_agent: DefaultAgentConfig # Name of the custom agent to activate when the session starts. # Must match the name of one of the agents in custom_agents. agent: str diff --git a/python/e2e/test_session.py b/python/e2e/test_session.py index 621062e4e..9e8440b9d 100644 --- a/python/e2e/test_session.py +++ b/python/e2e/test_session.py @@ -146,6 +146,34 @@ async def test_should_create_a_session_with_excludedTools(self, ctx: E2ETestCont assert "grep" in tool_names assert "view" not in tool_names + async def test_should_create_a_session_with_defaultAgent_excludedTools( + self, ctx: E2ETestContext + ): + secret_tool = Tool( + name="secret_tool", + description="A secret tool hidden from the default agent", + handler=lambda args: "SECRET", + parameters={ + "type": "object", + "properties": {"input": {"type": "string"}}, + }, + ) + + session = await ctx.client.create_session( + on_permission_request=PermissionHandler.approve_all, + tools=[secret_tool], + default_agent={"excluded_tools": ["secret_tool"]}, + ) + + await session.send("What is 1+1?") + await get_final_assistant_message(session) + + # The real assertion: verify the runtime excluded the tool from the CAPI request + traffic = await ctx.get_exchanges() + tools = traffic[0]["request"]["tools"] + tool_names = [t["function"]["name"] for t in tools] + assert "secret_tool" not in tool_names + # TODO: This test shows there's a race condition inside client.ts. If createSession # is called concurrently and autoStart is on, it may start multiple child processes. # This needs to be fixed. Right now it manifests as being unable to delete the temp diff --git a/test/scenarios/tools/custom-agents/README.md b/test/scenarios/tools/custom-agents/README.md index 41bb78c9e..391345454 100644 --- a/test/scenarios/tools/custom-agents/README.md +++ b/test/scenarios/tools/custom-agents/README.md @@ -1,26 +1,30 @@ # Config Sample: Custom Agents -Demonstrates configuring the Copilot SDK with **custom agent definitions** that restrict which tools an agent can use. This validates: +Demonstrates configuring the Copilot SDK with **custom agent definitions** that restrict which tools an agent can use, and **agent-exclusive tools** that are hidden from the main agent. This validates: 1. **Agent definition** — The `customAgents` session config accepts agent definitions with name, description, tool lists, and custom prompts. 2. **Tool scoping** — Each custom agent can be restricted to a subset of available tools (e.g. read-only tools like `grep`, `glob`, `view`). -3. **Agent awareness** — The model recognizes and can describe the configured custom agents. +3. **Agent-exclusive tools** — The `defaultAgent.excludedTools` option hides tools from the main agent while keeping them available to sub-agents. +4. **Agent awareness** — The model recognizes and can describe the configured custom agents. ## What Each Sample Does -1. Creates a session with a `customAgents` array containing a "researcher" agent -2. The researcher agent is scoped to read-only tools: `grep`, `glob`, `view` -3. Sends: _"What custom agents are available? Describe the researcher agent and its capabilities."_ -4. Prints the response — which should describe the researcher agent and its tool restrictions +1. Creates a session with a custom `analyze-codebase` tool and a `customAgents` array containing a "researcher" agent +2. Uses `defaultAgent.excludedTools` to hide `analyze-codebase` from the main agent +3. The researcher agent is scoped to read-only tools plus `analyze-codebase`: `grep`, `glob`, `view`, `analyze-codebase` +4. Sends: _"What custom agents are available? Describe the researcher agent and its capabilities."_ +5. Prints the response — which should describe the researcher agent and its tool restrictions ## Configuration | Option | Value | Effect | |--------|-------|--------| +| `tools` | `[analyze-codebase]` | Registers custom tool at session level | +| `defaultAgent.excludedTools` | `["analyze-codebase"]` | Hides tool from main agent | | `customAgents[0].name` | `"researcher"` | Internal identifier for the agent | | `customAgents[0].displayName` | `"Research Agent"` | Human-readable name | | `customAgents[0].description` | Custom text | Describes agent purpose | -| `customAgents[0].tools` | `["grep", "glob", "view"]` | Restricts agent to read-only tools | +| `customAgents[0].tools` | `["grep", "glob", "view", "analyze-codebase"]` | Restricts agent to read-only tools + analysis | | `customAgents[0].prompt` | Custom text | Sets agent behavior instructions | ## Run diff --git a/test/scenarios/tools/custom-agents/csharp/Program.cs b/test/scenarios/tools/custom-agents/csharp/Program.cs index c5c6525f1..8f6e2de6b 100644 --- a/test/scenarios/tools/custom-agents/csharp/Program.cs +++ b/test/scenarios/tools/custom-agents/csharp/Program.cs @@ -1,4 +1,6 @@ +using System.Text.Json; using GitHub.Copilot.SDK; +using Microsoft.Extensions.AI; var cliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"); @@ -12,9 +14,22 @@ try { + var analyzeCodebase = AIFunctionFactory.Create( + (string query) => $"Analysis result for: {query}", + new AIFunctionFactoryOptions + { + Name = "analyze-codebase", + Description = "Performs deep analysis of the codebase", + }); + await using var session = await client.CreateSessionAsync(new SessionConfig { Model = "claude-haiku-4.5", + Tools = [analyzeCodebase], + defaultAgent = new DefaultAgentConfig + { + ExcludedTools = ["analyze-codebase"], + }, CustomAgents = [ new CustomAgentConfig @@ -22,7 +37,7 @@ Name = "researcher", DisplayName = "Research Agent", Description = "A research agent that can only read and search files, not modify them", - Tools = ["grep", "glob", "view"], + Tools = ["grep", "glob", "view", "analyze-codebase"], Prompt = "You are a research assistant. You can search and read files but cannot modify anything. When asked about your capabilities, list the tools you have access to.", }, ], diff --git a/test/scenarios/tools/custom-agents/go/main.go b/test/scenarios/tools/custom-agents/go/main.go index d1769ff2b..1e6ada739 100644 --- a/test/scenarios/tools/custom-agents/go/main.go +++ b/test/scenarios/tools/custom-agents/go/main.go @@ -20,14 +20,29 @@ func main() { } defer client.Stop() + type AnalyzeParams struct { + Query string `json:"query" jsonschema:"the analysis query"` + } + + analyzeCodebase := copilot.DefineTool("analyze-codebase", + "Performs deep analysis of the codebase", + func(params AnalyzeParams, inv copilot.ToolInvocation) (string, error) { + return fmt.Sprintf("Analysis result for: %s", params.Query), nil + }, + ) + session, err := client.CreateSession(ctx, &copilot.SessionConfig{ Model: "claude-haiku-4.5", + Tools: []copilot.Tool{analyzeCodebase}, + DefaultAgent: &copilot.DefaultAgentConfig{ + ExcludedTools: []string{"analyze-codebase"}, + }, CustomAgents: []copilot.CustomAgentConfig{ { Name: "researcher", DisplayName: "Research Agent", Description: "A research agent that can only read and search files, not modify them", - Tools: []string{"grep", "glob", "view"}, + Tools: []string{"grep", "glob", "view", "analyze-codebase"}, Prompt: "You are a research assistant. You can search and read files but cannot modify anything. When asked about your capabilities, list the tools you have access to.", }, }, diff --git a/test/scenarios/tools/custom-agents/python/main.py b/test/scenarios/tools/custom-agents/python/main.py index d4c45950f..bf6e3978c 100644 --- a/test/scenarios/tools/custom-agents/python/main.py +++ b/test/scenarios/tools/custom-agents/python/main.py @@ -2,6 +2,11 @@ import os from copilot import CopilotClient from copilot.client import SubprocessConfig +from copilot.tools import Tool + + +async def analyze_handler(args): + return f"Analysis result for: {args.get('query', '')}" async def main(): @@ -12,18 +17,29 @@ async def main(): try: session = await client.create_session( - { - "model": "claude-haiku-4.5", - "custom_agents": [ - { - "name": "researcher", - "display_name": "Research Agent", - "description": "A research agent that can only read and search files, not modify them", - "tools": ["grep", "glob", "view"], - "prompt": "You are a research assistant. You can search and read files but cannot modify anything. When asked about your capabilities, list the tools you have access to.", + model="claude-haiku-4.5", + tools=[ + Tool( + name="analyze-codebase", + description="Performs deep analysis of the codebase", + handler=analyze_handler, + parameters={ + "type": "object", + "properties": {"query": {"type": "string"}}, }, - ], - } + ), + ], + default_agent={"excluded_tools": ["analyze-codebase"]}, + custom_agents=[ + { + "name": "researcher", + "display_name": "Research Agent", + "description": "A research agent that can only read and search files, not modify them", + "tools": ["grep", "glob", "view", "analyze-codebase"], + "prompt": "You are a research assistant. You can search and read files but cannot modify anything. When asked about your capabilities, list the tools you have access to.", + }, + ], + on_permission_request=lambda _: {"action": "allow"}, ) response = await session.send_and_wait( diff --git a/test/scenarios/tools/custom-agents/typescript/src/index.ts b/test/scenarios/tools/custom-agents/typescript/src/index.ts index f6e163256..f3cf01393 100644 --- a/test/scenarios/tools/custom-agents/typescript/src/index.ts +++ b/test/scenarios/tools/custom-agents/typescript/src/index.ts @@ -1,4 +1,14 @@ -import { CopilotClient } from "@github/copilot-sdk"; +import { CopilotClient, defineTool } from "@github/copilot-sdk"; +import { z } from "zod"; + +const analyzeCodebase = defineTool({ + name: "analyze-codebase", + description: "Performs deep analysis of the codebase, generating extensive context", + parameters: z.object({ query: z.string().describe("The analysis query") }), + handler: async ({ query }) => { + return `Analysis result for: ${query}`; + }, +}); async function main() { const client = new CopilotClient({ @@ -9,12 +19,16 @@ async function main() { try { const session = await client.createSession({ model: "claude-haiku-4.5", + tools: [analyzeCodebase], + defaultAgent: { + excludedTools: ["analyze-codebase"], + }, customAgents: [ { name: "researcher", displayName: "Research Agent", description: "A research agent that can only read and search files, not modify them", - tools: ["grep", "glob", "view"], + tools: ["grep", "glob", "view", "analyze-codebase"], prompt: "You are a research assistant. You can search and read files but cannot modify anything. When asked about your capabilities, list the tools you have access to.", }, ], diff --git a/test/snapshots/mcp_and_agents/should_accept_defaultagent_configuration_on_session_resume.yaml b/test/snapshots/mcp_and_agents/should_accept_defaultagent_configuration_on_session_resume.yaml new file mode 100644 index 000000000..65fe6664e --- /dev/null +++ b/test/snapshots/mcp_and_agents/should_accept_defaultagent_configuration_on_session_resume.yaml @@ -0,0 +1,14 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: What is 3+3? + - role: assistant + content: 3 + 3 = 6 + - role: user + content: What is 4+4? + - role: assistant + content: 4 + 4 = 8 diff --git a/test/snapshots/mcp_and_agents/should_hide_excluded_tools_from_default_agent.yaml b/test/snapshots/mcp_and_agents/should_hide_excluded_tools_from_default_agent.yaml new file mode 100644 index 000000000..f5506bb18 --- /dev/null +++ b/test/snapshots/mcp_and_agents/should_hide_excluded_tools_from_default_agent.yaml @@ -0,0 +1,10 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: Do you have access to a tool called secret_tool? Answer yes or no. + - role: assistant + content: No, I don't have access to a tool called secret_tool. diff --git a/test/snapshots/session/should_create_a_session_with_defaultagent_excludedtools.yaml b/test/snapshots/session/should_create_a_session_with_defaultagent_excludedtools.yaml new file mode 100644 index 000000000..250402101 --- /dev/null +++ b/test/snapshots/session/should_create_a_session_with_defaultagent_excludedtools.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 6e17a80a0ee8ac9b1e332c322afe5d1553cee9f4 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Thu, 16 Apr 2026 15:20:40 -0700 Subject: [PATCH 2/4] fix: address CI validation failures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/custom-agents.md | 4 ++-- nodejs/test/e2e/mcp_and_agents.test.ts | 11 ++--------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/docs/features/custom-agents.md b/docs/features/custom-agents.md index e8c73a5cd..a5c41c91e 100644 --- a/docs/features/custom-agents.md +++ b/docs/features/custom-agents.md @@ -840,7 +840,7 @@ session = await client.create_session( ```go session, err := client.CreateSession(ctx, &copilot.SessionConfig{ Tools: []copilot.Tool{heavyTool}, - defaultAgent: &copilot.DefaultAgentConfig{ + DefaultAgent: &copilot.DefaultAgentConfig{ ExcludedTools: []string{"analyze-codebase"}, }, CustomAgents: []copilot.CustomAgentConfig{ @@ -863,7 +863,7 @@ session, err := client.CreateSession(ctx, &copilot.SessionConfig{ var session = await client.CreateSessionAsync(new SessionConfig { Tools = [analyzeCodebaseTool], - defaultAgent = new DefaultAgentConfig + DefaultAgent = new DefaultAgentConfig { ExcludedTools = ["analyze-codebase"], }, diff --git a/nodejs/test/e2e/mcp_and_agents.test.ts b/nodejs/test/e2e/mcp_and_agents.test.ts index 45d505136..aa580cdee 100644 --- a/nodejs/test/e2e/mcp_and_agents.test.ts +++ b/nodejs/test/e2e/mcp_and_agents.test.ts @@ -6,12 +6,7 @@ import { dirname, resolve } from "path"; import { fileURLToPath } from "url"; import { describe, expect, it } from "vitest"; import { z } from "zod"; -import type { - CustomAgentConfig, - DefaultAgentConfig, - MCPStdioServerConfig, - MCPServerConfig, -} from "../../src/index.js"; +import type { CustomAgentConfig, MCPStdioServerConfig, MCPServerConfig } from "../../src/index.js"; import { approveAll, defineTool } from "../../src/index.js"; import { createSdkTestContext } from "./harness/sdkTestContext.js"; @@ -332,9 +327,7 @@ describe("MCP Servers and Custom Agents", async () => { // The real assertion: verify the runtime excluded the tool from the CAPI request const exchanges = await openAiEndpoint.getExchanges(); const toolNames = exchanges.flatMap((e) => - (e.request.tools ?? []).map((t) => - "function" in t ? t.function.name : "", - ), + (e.request.tools ?? []).map((t) => ("function" in t ? t.function.name : "")) ); expect(toolNames).not.toContain("secret_tool"); From 4a96033e0050facedf21fa5cee8f78f9cbf4a207 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Thu, 16 Apr 2026 15:43:53 -0700 Subject: [PATCH 3/4] fix: address PR review feedback - Fix defineTool signature in TS scenario and docs (name, config) - Remove unused System.Text.Json import from C# scenario Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/custom-agents.md | 3 +-- test/scenarios/tools/custom-agents/csharp/Program.cs | 1 - test/scenarios/tools/custom-agents/typescript/src/index.ts | 3 +-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/features/custom-agents.md b/docs/features/custom-agents.md index a5c41c91e..37d7158e8 100644 --- a/docs/features/custom-agents.md +++ b/docs/features/custom-agents.md @@ -775,8 +775,7 @@ This is useful when: import { CopilotClient, defineTool, approveAll } from "@github/copilot-sdk"; import { z } from "zod"; -const heavyContextTool = defineTool({ - name: "analyze-codebase", +const heavyContextTool = defineTool("analyze-codebase", { description: "Performs deep analysis of the codebase, generating extensive context", parameters: z.object({ query: z.string() }), handler: async ({ query }) => { diff --git a/test/scenarios/tools/custom-agents/csharp/Program.cs b/test/scenarios/tools/custom-agents/csharp/Program.cs index 8f6e2de6b..6240c8958 100644 --- a/test/scenarios/tools/custom-agents/csharp/Program.cs +++ b/test/scenarios/tools/custom-agents/csharp/Program.cs @@ -1,4 +1,3 @@ -using System.Text.Json; using GitHub.Copilot.SDK; using Microsoft.Extensions.AI; diff --git a/test/scenarios/tools/custom-agents/typescript/src/index.ts b/test/scenarios/tools/custom-agents/typescript/src/index.ts index f3cf01393..ffb0bd827 100644 --- a/test/scenarios/tools/custom-agents/typescript/src/index.ts +++ b/test/scenarios/tools/custom-agents/typescript/src/index.ts @@ -1,8 +1,7 @@ import { CopilotClient, defineTool } from "@github/copilot-sdk"; import { z } from "zod"; -const analyzeCodebase = defineTool({ - name: "analyze-codebase", +const analyzeCodebase = defineTool("analyze-codebase", { description: "Performs deep analysis of the codebase, generating extensive context", parameters: z.object({ query: z.string().describe("The analysis query") }), handler: async ({ query }) => { From f8348305052d365ee9144c181bc9e4e0cd2ce76d Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Thu, 16 Apr 2026 16:04:32 -0700 Subject: [PATCH 4/4] fix: address remaining CI validation failures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/custom-agents.md | 2 ++ test/scenarios/tools/custom-agents/csharp/Program.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/features/custom-agents.md b/docs/features/custom-agents.md index 37d7158e8..0d27fe873 100644 --- a/docs/features/custom-agents.md +++ b/docs/features/custom-agents.md @@ -836,6 +836,7 @@ session = await client.create_session(
Go + ```go session, err := client.CreateSession(ctx, &copilot.SessionConfig{ Tools: []copilot.Tool{heavyTool}, @@ -858,6 +859,7 @@ session, err := client.CreateSession(ctx, &copilot.SessionConfig{
C# / .NET + ```csharp var session = await client.CreateSessionAsync(new SessionConfig { diff --git a/test/scenarios/tools/custom-agents/csharp/Program.cs b/test/scenarios/tools/custom-agents/csharp/Program.cs index 6240c8958..d3c068ade 100644 --- a/test/scenarios/tools/custom-agents/csharp/Program.cs +++ b/test/scenarios/tools/custom-agents/csharp/Program.cs @@ -25,7 +25,7 @@ { Model = "claude-haiku-4.5", Tools = [analyzeCodebase], - defaultAgent = new DefaultAgentConfig + DefaultAgent = new DefaultAgentConfig { ExcludedTools = ["analyze-codebase"], },