diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 0e1ec145e..2612beaa3 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -1088,6 +1088,7 @@ public async Task CreateSessionAsync(SessionConfig config, Cance InstructionDirectories: config.InstructionDirectories, PluginDirectories: config.PluginDirectories, LargeOutput: config.LargeOutput, + ToolSearch: config.ToolSearch, Memory: config.Memory, Canvases: config.Canvases, RequestCanvasRenderer: config.RequestCanvasRenderer, @@ -1297,6 +1298,7 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes InstructionDirectories: config.InstructionDirectories, PluginDirectories: config.PluginDirectories, LargeOutput: config.LargeOutput, + ToolSearch: config.ToolSearch, Memory: config.Memory, Canvases: config.Canvases, RequestCanvasRenderer: config.RequestCanvasRenderer, @@ -2630,6 +2632,7 @@ internal record CreateSessionRequest( IList? InstructionDirectories = null, IList? PluginDirectories = null, LargeToolOutputConfig? LargeOutput = null, + ToolSearchConfig? ToolSearch = null, MemoryConfiguration? Memory = null, #pragma warning disable GHCP001 IList? Canvases = null, @@ -2729,6 +2732,7 @@ internal record ResumeSessionRequest( IList? InstructionDirectories = null, IList? PluginDirectories = null, LargeToolOutputConfig? LargeOutput = null, + ToolSearchConfig? ToolSearch = null, MemoryConfiguration? Memory = null, #pragma warning disable GHCP001 IList? Canvases = null, diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index bcd8903c8..d9d255567 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -679,6 +679,12 @@ public sealed class ToolResultObject [JsonPropertyName("toolTelemetry")] public IDictionary? ToolTelemetry { get; set; } + /// + /// Names of tools returned by a tool-search tool. + /// + [JsonPropertyName("toolReferences")] + public IList? ToolReferences { get; set; } + /// /// Converts the result of an invocation into a /// . Handles , @@ -2703,6 +2709,30 @@ public sealed class LargeToolOutputConfig public string? OutputDirectory { get; set; } } +/// +/// Overrides the runtime's built-in tool-search behavior. +/// Defers tools to keep the model's active tool set small. +/// To override the tool-search tool's implementation, register a tool +/// named "tool_search_tool" with OverridesBuiltInTool set to +/// . +/// +public sealed class ToolSearchConfig +{ + /// + /// Enable or disable tool search. + /// + [JsonPropertyName("enabled")] + public bool? Enabled { get; set; } + + /// + /// The tool count above which MCP and external tools are deferred behind + /// tool search. When , the runtime default (30) + /// applies. + /// + [JsonPropertyName("deferThreshold")] + public int? DeferThreshold { get; set; } +} + /// /// Configuration for session memory. /// @@ -2811,6 +2841,7 @@ protected SessionConfigBase(SessionConfigBase? other) Hooks = other.Hooks; InfiniteSessions = other.InfiniteSessions; LargeOutput = other.LargeOutput; + ToolSearch = other.ToolSearch; Memory = other.Memory; McpServers = other.McpServers is not null ? (other.McpServers is Dictionary dict @@ -3215,6 +3246,13 @@ protected SessionConfigBase(SessionConfigBase? other) /// public LargeToolOutputConfig? LargeOutput { get; set; } + /// + /// Overrides the runtime's built-in tool-search behavior. + /// Tool search defers tools to keep the model's active tool set small. When , + /// the runtime default applies. + /// + public ToolSearchConfig? ToolSearch { get; set; } + /// /// Configuration for session memory. When set, controls whether the /// session can read and write persistent memory. diff --git a/go/client.go b/go/client.go index 5b8dabf8f..a7ebc16f2 100644 --- a/go/client.go +++ b/go/client.go @@ -722,6 +722,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses req.DisabledSkills = config.DisabledSkills req.InfiniteSessions = config.InfiniteSessions req.LargeOutput = config.LargeOutput + req.ToolSearch = config.ToolSearch req.Memory = config.Memory req.GitHubToken = config.GitHubToken req.RemoteSession = config.RemoteSession @@ -1085,6 +1086,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, req.DisabledSkills = config.DisabledSkills req.InfiniteSessions = config.InfiniteSessions req.LargeOutput = config.LargeOutput + req.ToolSearch = config.ToolSearch req.Memory = config.Memory req.GitHubToken = config.GitHubToken req.RemoteSession = config.RemoteSession diff --git a/go/session.go b/go/session.go index 808876761..e41c317d2 100644 --- a/go/session.go +++ b/go/session.go @@ -1542,6 +1542,7 @@ func (s *Session) executeToolAndRespond(requestID, toolName, toolCallID string, TextResultForLlm: textResultForLLM, ToolTelemetry: result.ToolTelemetry, ResultType: &effectiveResultType, + ToolReferences: result.ToolReferences, } if result.Error != "" { rpcResult.Error = &result.Error diff --git a/go/types.go b/go/types.go index 1e424514e..855d1a195 100644 --- a/go/types.go +++ b/go/types.go @@ -948,6 +948,18 @@ type LargeToolOutputConfig struct { OutputDirectory string `json:"outputDir,omitempty"` } +// ToolSearchConfig allows to configure tool search behavior. +// Tool search defers tools to keep the model's active tool set small. +// To override the tool-search tool's implementation, register a +// [Tool] named "tool_search_tool" with OverridesBuiltInTool set to true. +type ToolSearchConfig struct { + // Controls whether tool search is enabled. + Enabled *bool `json:"enabled,omitempty"` + // DeferThreshold is the tool count above which MCP and external tools are + // deferred behind tool search. When nil, the runtime default (30) applies. + DeferThreshold *int `json:"deferThreshold,omitempty"` +} + // SessionFSCapabilities declares optional provider capabilities. type SessionFSCapabilities struct { // Sqlite indicates whether the provider supports SQLite query/exists operations. @@ -1153,6 +1165,10 @@ type SessionConfig struct { // output exceeding the configured size, the output is written to a temp file // and a reference is returned to the model instead of the full payload. LargeOutput *LargeToolOutputConfig + // ToolSearch overrides the runtime's built-in tool-search behavior, which + // defers rarely used tools behind a searchable index. When nil, the runtime + // default applies. + ToolSearch *ToolSearchConfig // Memory configures the memory feature for the session. When omitted, the // runtime default applies. Memory *MemoryConfiguration @@ -1300,6 +1316,8 @@ type ToolResult struct { Error string `json:"error,omitempty"` SessionLog string `json:"sessionLog,omitempty"` ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` + // ToolReferences lists names of tools returned by a tool-search tool. + ToolReferences []string `json:"toolReferences,omitempty"` } // CommandContext provides context about a slash-command invocation. @@ -1596,6 +1614,10 @@ type ResumeSessionConfig struct { // output exceeding the configured size, the output is written to a temp file // and a reference is returned to the model instead of the full payload. LargeOutput *LargeToolOutputConfig + // ToolSearch overrides the runtime's built-in tool-search behavior, which + // defers rarely used tools behind a searchable index. When nil, the runtime + // default applies. + ToolSearch *ToolSearchConfig // Memory configures the memory feature for the session. When omitted, the // runtime default applies. Memory *MemoryConfiguration @@ -2109,6 +2131,7 @@ type createSessionRequest struct { DisabledSkills []string `json:"disabledSkills,omitempty"` InfiniteSessions *InfiniteSessionConfig `json:"infiniteSessions,omitempty"` LargeOutput *LargeToolOutputConfig `json:"largeOutput,omitempty"` + ToolSearch *ToolSearchConfig `json:"toolSearch,omitempty"` Memory *MemoryConfiguration `json:"memory,omitempty"` Commands []wireCommand `json:"commands,omitempty"` RequestElicitation *bool `json:"requestElicitation,omitempty"` @@ -2198,6 +2221,7 @@ type resumeSessionRequest struct { DisabledSkills []string `json:"disabledSkills,omitempty"` InfiniteSessions *InfiniteSessionConfig `json:"infiniteSessions,omitempty"` LargeOutput *LargeToolOutputConfig `json:"largeOutput,omitempty"` + ToolSearch *ToolSearchConfig `json:"toolSearch,omitempty"` Memory *MemoryConfiguration `json:"memory,omitempty"` Commands []wireCommand `json:"commands,omitempty"` RequestElicitation *bool `json:"requestElicitation,omitempty"` diff --git a/java/src/main/java/com/github/copilot/SessionRequestBuilder.java b/java/src/main/java/com/github/copilot/SessionRequestBuilder.java index f88bf2a85..5e2f801c2 100644 --- a/java/src/main/java/com/github/copilot/SessionRequestBuilder.java +++ b/java/src/main/java/com/github/copilot/SessionRequestBuilder.java @@ -145,6 +145,7 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess request.setInstructionDirectories(config.getInstructionDirectories()); request.setPluginDirectories(config.getPluginDirectories()); request.setLargeOutput(config.getLargeOutput()); + request.setToolSearch(config.getToolSearch()); request.setMemory(config.getMemory()); request.setDisabledSkills(config.getDisabledSkills()); request.setConfigDirectory(config.getConfigDirectory()); @@ -280,6 +281,7 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo request.setInstructionDirectories(config.getInstructionDirectories()); request.setPluginDirectories(config.getPluginDirectories()); request.setLargeOutput(config.getLargeOutput()); + request.setToolSearch(config.getToolSearch()); request.setMemory(config.getMemory()); request.setDisabledSkills(config.getDisabledSkills()); request.setInfiniteSessions(config.getInfiniteSessions()); diff --git a/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java b/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java index 2510b0bd6..9412c916c 100644 --- a/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java +++ b/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java @@ -138,6 +138,9 @@ public final class CreateSessionRequest { @JsonProperty("largeOutput") private LargeToolOutputConfig largeOutput; + @JsonProperty("toolSearch") + private ToolSearchConfig toolSearch; + @JsonProperty("memory") private MemoryConfiguration memory; @@ -620,6 +623,16 @@ public void setLargeOutput(LargeToolOutputConfig largeOutput) { this.largeOutput = largeOutput; } + /** Gets tool-search config. @return the tool-search config */ + public ToolSearchConfig getToolSearch() { + return toolSearch; + } + + /** Sets tool-search config. @param toolSearch the tool-search config */ + public void setToolSearch(ToolSearchConfig toolSearch) { + this.toolSearch = toolSearch; + } + /** Gets memory config. @return the memory config */ public MemoryConfiguration getMemory() { return memory; diff --git a/java/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java b/java/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java index 83b365a41..29335c668 100644 --- a/java/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java +++ b/java/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java @@ -90,6 +90,7 @@ public class ResumeSessionConfig { private List instructionDirectories; private List pluginDirectories; private LargeToolOutputConfig largeOutput; + private ToolSearchConfig toolSearch; private MemoryConfiguration memory; private List disabledSkills; private InfiniteSessionConfig infiniteSessions; @@ -1446,6 +1447,27 @@ public ResumeSessionConfig setLargeOutput(LargeToolOutputConfig largeOutput) { return this; } + /** + * Gets the tool-search configuration. + * + * @return the tool-search config, or {@code null} for the runtime default + */ + public ToolSearchConfig getToolSearch() { + return toolSearch; + } + + /** + * Sets the tool-search configuration. + * + * @param toolSearch + * the tool-search config + * @return this config for method chaining + */ + public ResumeSessionConfig setToolSearch(ToolSearchConfig toolSearch) { + this.toolSearch = toolSearch; + return this; + } + /** * Gets the configuration for session memory. * @@ -1806,6 +1828,7 @@ public ResumeSessionConfig clone() { : null; copy.pluginDirectories = this.pluginDirectories != null ? new ArrayList<>(this.pluginDirectories) : null; copy.largeOutput = this.largeOutput; + copy.toolSearch = this.toolSearch; copy.memory = this.memory; copy.disabledSkills = this.disabledSkills != null ? new ArrayList<>(this.disabledSkills) : null; copy.infiniteSessions = this.infiniteSessions; diff --git a/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java b/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java index 4917f1d8c..07039ce79 100644 --- a/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java +++ b/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java @@ -178,6 +178,9 @@ public final class ResumeSessionRequest { @JsonProperty("largeOutput") private LargeToolOutputConfig largeOutput; + @JsonProperty("toolSearch") + private ToolSearchConfig toolSearch; + @JsonProperty("memory") private MemoryConfiguration memory; @@ -836,6 +839,16 @@ public void setLargeOutput(LargeToolOutputConfig largeOutput) { this.largeOutput = largeOutput; } + /** Gets tool-search config. @return the tool-search config */ + public ToolSearchConfig getToolSearch() { + return toolSearch; + } + + /** Sets tool-search config. @param toolSearch the tool-search config */ + public void setToolSearch(ToolSearchConfig toolSearch) { + this.toolSearch = toolSearch; + } + /** Gets memory config. @return the memory config */ public MemoryConfiguration getMemory() { return memory; diff --git a/java/src/main/java/com/github/copilot/rpc/SessionConfig.java b/java/src/main/java/com/github/copilot/rpc/SessionConfig.java index cc27386c0..59390cc8a 100644 --- a/java/src/main/java/com/github/copilot/rpc/SessionConfig.java +++ b/java/src/main/java/com/github/copilot/rpc/SessionConfig.java @@ -80,6 +80,7 @@ public class SessionConfig { private List instructionDirectories; private List pluginDirectories; private LargeToolOutputConfig largeOutput; + private ToolSearchConfig toolSearch; private MemoryConfiguration memory; private List disabledSkills; private String configDirectory; @@ -1129,6 +1130,28 @@ public SessionConfig setLargeOutput(LargeToolOutputConfig largeOutput) { return this; } + /** + * Gets the tool-search override configuration. + * + * @return the tool-search config, or {@code null} for the runtime default + */ + public ToolSearchConfig getToolSearch() { + return toolSearch; + } + + /** + * Sets the tool-search override configuration. When {@code null}, the runtime + * default tool-search behavior applies. + * + * @param toolSearch + * the tool-search config + * @return this config instance for method chaining + */ + public SessionConfig setToolSearch(ToolSearchConfig toolSearch) { + this.toolSearch = toolSearch; + return this; + } + /** * Gets the configuration for session memory. * @@ -1931,6 +1954,7 @@ public SessionConfig clone() { : null; copy.pluginDirectories = this.pluginDirectories != null ? new ArrayList<>(this.pluginDirectories) : null; copy.largeOutput = this.largeOutput; + copy.toolSearch = this.toolSearch; copy.memory = this.memory; copy.disabledSkills = this.disabledSkills != null ? new ArrayList<>(this.disabledSkills) : null; copy.configDirectory = this.configDirectory; diff --git a/java/src/main/java/com/github/copilot/rpc/ToolResultObject.java b/java/src/main/java/com/github/copilot/rpc/ToolResultObject.java index e55ff9ab6..497e42aca 100644 --- a/java/src/main/java/com/github/copilot/rpc/ToolResultObject.java +++ b/java/src/main/java/com/github/copilot/rpc/ToolResultObject.java @@ -31,7 +31,7 @@ *

Example: Custom Result

* *
{@code
- * return new ToolResultObject("success", "Result text", null, null, null, null);
+ * return new ToolResultObject("success", "Result text", null, null, null, null, null);
  * }
* * @param resultType @@ -46,6 +46,8 @@ * the session log text * @param toolTelemetry * the tool telemetry data + * @param toolReferences + * names of tools returned by a tool-search tool * @see ToolHandler * @see ToolBinaryResult * @since 1.0.0 @@ -55,7 +57,8 @@ public record ToolResultObject(@JsonProperty("resultType") String resultType, @JsonProperty("textResultForLlm") String textResultForLlm, @JsonProperty("binaryResultsForLlm") List binaryResultsForLlm, @JsonProperty("error") String error, @JsonProperty("sessionLog") String sessionLog, - @JsonProperty("toolTelemetry") Map toolTelemetry) { + @JsonProperty("toolTelemetry") Map toolTelemetry, + @JsonProperty("toolReferences") List toolReferences) { /** * Creates a success result with the given text. @@ -65,7 +68,7 @@ public record ToolResultObject(@JsonProperty("resultType") String resultType, * @return a success result */ public static ToolResultObject success(String textResultForLlm) { - return new ToolResultObject("success", textResultForLlm, null, null, null, null); + return new ToolResultObject("success", textResultForLlm, null, null, null, null, null); } /** @@ -76,7 +79,7 @@ public static ToolResultObject success(String textResultForLlm) { * @return an error result */ public static ToolResultObject error(String error) { - return new ToolResultObject("error", null, null, error, null, null); + return new ToolResultObject("error", null, null, error, null, null, null); } /** @@ -89,7 +92,7 @@ public static ToolResultObject error(String error) { * @return an error result */ public static ToolResultObject error(String textResultForLlm, String error) { - return new ToolResultObject("error", textResultForLlm, null, error, null, null); + return new ToolResultObject("error", textResultForLlm, null, error, null, null, null); } /** @@ -106,6 +109,6 @@ public static ToolResultObject error(String textResultForLlm, String error) { * @return a failure result */ public static ToolResultObject failure(String textResultForLlm, String error) { - return new ToolResultObject("failure", textResultForLlm, null, error, null, null); + return new ToolResultObject("failure", textResultForLlm, null, error, null, null, null); } } diff --git a/java/src/main/java/com/github/copilot/rpc/ToolSearchConfig.java b/java/src/main/java/com/github/copilot/rpc/ToolSearchConfig.java new file mode 100644 index 000000000..dd27ddf86 --- /dev/null +++ b/java/src/main/java/com/github/copilot/rpc/ToolSearchConfig.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +package com.github.copilot.rpc; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Overrides the runtime's built-in tool-search behavior. + *

+ * Tool search defers tools to keep the model's active tool set small. To + * override the tool-search tool's implementation, register a tool named + * {@code "tool_search_tool"} with {@code overridesBuiltInTool} set to + * {@code true}. + * + * @since 1.3.0 + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ToolSearchConfig { + + @JsonProperty("enabled") + private Boolean enabled; + + @JsonProperty("deferThreshold") + private Integer deferThreshold; + + /** + * Gets whether tool search is enabled. + * + * @return {@code true} if enabled, {@code false} if disabled, or {@code null} + * for the runtime default + */ + public Boolean getEnabled() { + return enabled; + } + + /** + * Toggle that enables or disables tool search. + * + * @param enabled + * {@code true} to enable, {@code false} to disable + * @return this config for method chaining + */ + public ToolSearchConfig setEnabled(Boolean enabled) { + this.enabled = enabled; + return this; + } + + /** + * Gets the tool count above which MCP and external tools are deferred behind + * tool search. + * + * @return the defer threshold, or {@code null} for the runtime default (30) + */ + public Integer getDeferThreshold() { + return deferThreshold; + } + + /** + * Sets the tool count above which MCP and external tools are deferred behind + * tool search. Defaults to the runtime default (30) when unset. + * + * @param deferThreshold + * the threshold value + * @return this config for method chaining + */ + public ToolSearchConfig setDeferThreshold(Integer deferThreshold) { + this.deferThreshold = deferThreshold; + return this; + } +} diff --git a/java/src/test/java/com/github/copilot/ToolResultsTest.java b/java/src/test/java/com/github/copilot/ToolResultsTest.java index 54216d921..8278fdf28 100644 --- a/java/src/test/java/com/github/copilot/ToolResultsTest.java +++ b/java/src/test/java/com/github/copilot/ToolResultsTest.java @@ -68,7 +68,7 @@ void testShouldHandleToolResultWithRejectedResultType() throws Exception { toolHandlerCalled[0] = true; return CompletableFuture.completedFuture(new ToolResultObject("rejected", "Deployment rejected: policy violation - production deployments require approval", null, - null, null, null)); + null, null, null, null)); }); try (CopilotClient client = ctx.createClient()) { @@ -116,7 +116,7 @@ void testShouldHandleToolResultWithDeniedResultType() throws Exception { (invocation) -> { toolHandlerCalled[0] = true; return CompletableFuture.completedFuture(new ToolResultObject("denied", - "Access denied: insufficient permissions to read secrets", null, null, null, null)); + "Access denied: insufficient permissions to read secrets", null, null, null, null, null)); }); try (CopilotClient client = ctx.createClient()) { diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index 9f430600c..fdf2c450d 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -1410,6 +1410,7 @@ export class CopilotClient { skipPermission: tool.skipPermission, defer: tool.defer, })), + toolSearch: config.toolSearch, canvases: config.canvases?.map((canvas) => canvas.declaration), requestCanvasRenderer: config.requestCanvasRenderer, requestExtensions: config.requestExtensions, @@ -1626,6 +1627,7 @@ export class CopilotClient { skipPermission: tool.skipPermission, defer: tool.defer, })), + toolSearch: config.toolSearch, canvases: config.canvases?.map((canvas) => canvas.declaration), requestCanvasRenderer: config.requestCanvasRenderer, requestExtensions: config.requestExtensions, diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts index e05b33c15..a5dd70f57 100644 --- a/nodejs/src/index.ts +++ b/nodejs/src/index.ts @@ -149,6 +149,7 @@ export type { ToolInvocation, ToolTelemetry, ToolResultObject, + ToolSearchConfig, TypedSessionEventHandler, TypedSessionLifecycleHandler, ZodSchema, diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 97182b5f1..6149107bc 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -403,6 +403,10 @@ export type ToolResultObject = { error?: string; sessionLog?: string; toolTelemetry?: ToolTelemetry; + /** + * Names of tools returned by a tool-search tool. + */ + toolReferences?: string[]; }; export type ToolResult = string | ToolResultObject; @@ -599,6 +603,35 @@ export function defineTool( return { name, ...config }; } +/** + * SDK-supplied override for the runtime's built-in tool-search behavior. + * + * Tool search lets the model discover tools on demand instead of loading every + * tool definition up front. When the total tool count exceeds the deferral + * threshold, MCP and external tools are marked as deferred and surfaced through + * the built-in `tool_search_tool`. + * + * To override the tool-search tool's model-facing definition and/or its + * execution, register a {@link Tool} named `tool_search_tool` with + * `overridesBuiltInTool: true`. To customize the in-prompt tool-search + * guidance, use the `tool_instructions` section of {@link SystemMessageConfig} + * in `"customize"` mode. + */ +export interface ToolSearchConfig { + /** + * Toggle to enable/disable tool search. When disabled, all tools are pre-loaded + * and the model's active tool set is not deferred. + */ + enabled?: boolean; + + /** + * Overrides the total tool count at which MCP and external tools are + * automatically deferred behind tool search. Defaults to the built-in + * threshold (30) when omitted. + */ + deferThreshold?: number; +} + // ============================================================================ // Commands // ============================================================================ @@ -1871,6 +1904,15 @@ export interface SessionConfigBase { */ systemMessage?: SystemMessageConfig; + /** + * Override for the runtime's built-in tool-search behavior. + * + * To also override the tool-search tool's implementation, register a + * {@link Tool} named `tool_search_tool` with `overridesBuiltInTool: true` in + * {@link SessionConfigBase.tools}. + */ + toolSearch?: ToolSearchConfig; + /** * List of tool names to allow. When specified, only these tools will be available. * diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py index fdf422d2a..550de6dbb 100644 --- a/python/copilot/__init__.py +++ b/python/copilot/__init__.py @@ -156,6 +156,7 @@ SessionUiApi, SessionUiCapabilities, SystemMessageConfig, + ToolSearchConfig, UserInputHandler, UserInputRequest, UserInputResponse, @@ -330,6 +331,7 @@ "ToolInvocation", "ToolResult", "ToolResultType", + "ToolSearchConfig", "ToolSet", "UriRuntimeConnection", "UserInputHandler", diff --git a/python/copilot/client.py b/python/copilot/client.py index 55d01c5b5..38a05cf0f 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -106,6 +106,7 @@ SessionHooks, SessionLimitsConfig, SystemMessageConfig, + ToolSearchConfig, UserInputHandler, _capabilities_to_dict, _PermissionHandlerFn, @@ -254,6 +255,16 @@ def _session_limits_to_wire(config: Mapping[str, Any]) -> dict[str, Any]: return wire +def _tool_search_to_wire(config: Mapping[str, Any]) -> dict[str, Any]: + """Convert a ``ToolSearchConfig`` mapping to wire format.""" + wire: dict[str, Any] = {} + if "enabled" in config: + wire["enabled"] = config["enabled"] + if "defer_threshold" in config: + wire["deferThreshold"] = config["defer_threshold"] + return wire + + class TelemetryConfig(TypedDict, total=False): """Configuration for OpenTelemetry integration with the Copilot CLI.""" @@ -1696,6 +1707,7 @@ async def create_session( context_tier: ContextTier | None = None, tools: list[Tool] | None = None, system_message: SystemMessageConfig | None = None, + tool_search: ToolSearchConfig | None = None, available_tools: list[str] | ToolSet | None = None, excluded_tools: list[str] | ToolSet | None = None, on_user_input_request: UserInputHandler | None = None, @@ -1970,6 +1982,9 @@ async def create_session( if wire_system_message: payload["systemMessage"] = wire_system_message + if tool_search is not None: + payload["toolSearch"] = _tool_search_to_wire(tool_search) + if available_tools is not None: payload["availableTools"] = available_tools if excluded_tools is not None: @@ -2347,6 +2362,7 @@ async def resume_session( context_tier: ContextTier | None = None, tools: list[Tool] | None = None, system_message: SystemMessageConfig | None = None, + tool_search: ToolSearchConfig | None = None, available_tools: list[str] | ToolSet | None = None, excluded_tools: list[str] | ToolSet | None = None, on_user_input_request: UserInputHandler | None = None, @@ -2621,6 +2637,8 @@ async def resume_session( wire_system_message, transform_callbacks = _extract_transform_callbacks(system_message) if wire_system_message: payload["systemMessage"] = wire_system_message + if tool_search is not None: + payload["toolSearch"] = _tool_search_to_wire(tool_search) if available_tools is not None: payload["availableTools"] = available_tools if excluded_tools is not None: diff --git a/python/copilot/session.py b/python/copilot/session.py index 50de96f19..046990e41 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -1134,6 +1134,28 @@ class LargeToolOutputConfig(TypedDict, total=False): output_directory: str +class ToolSearchConfig(TypedDict, total=False): + """ + Override for the runtime's built-in tool-search behavior. + + Tool search lets the model discover tools on demand instead of loading every + tool definition up front. When the total tool count exceeds the deferral + threshold, MCP and external tools are marked as deferred and surfaced through + the built-in ``tool_search_tool``. + + To override the tool-search tool's implementation, register a :class:`Tool` + named ``tool_search_tool`` with ``overrides_built_in_tool=True``. To customize + the in-prompt tool-search guidance, use the ``tool_instructions`` section of + the system message in ``"customize"`` mode. + """ + + # Toggle that enables or disables tool search. + enabled: bool + # Overrides the total tool count at which MCP and external tools are + # automatically deferred behind tool search. + defer_threshold: int + + class MemoryConfiguration(TypedDict): """ Configuration for session memory. @@ -1997,6 +2019,7 @@ async def _execute_tool_and_respond( text_result_for_llm=tool_result.text_result_for_llm, error=tool_result.error, result_type=tool_result.result_type, + tool_references=tool_result.tool_references, tool_telemetry=tool_result.tool_telemetry, ), ) diff --git a/python/copilot/tools.py b/python/copilot/tools.py index a82a48b1e..9165e4bc3 100644 --- a/python/copilot/tools.py +++ b/python/copilot/tools.py @@ -38,6 +38,7 @@ class ToolResult: binary_results_for_llm: list[ToolBinaryResult] | None = None session_log: str | None = None tool_telemetry: dict[str, Any] | None = None + tool_references: list[str] | None = None _from_exception: bool = field(default=False, repr=False) diff --git a/rust/src/session.rs b/rust/src/session.rs index d0fadd044..8407353e6 100644 --- a/rust/src/session.rs +++ b/rust/src/session.rs @@ -1515,6 +1515,7 @@ fn tool_failure_result(message: impl Into) -> ToolResult { session_log: None, error: Some(message), tool_telemetry: None, + tool_references: None, }) } diff --git a/rust/src/tool.rs b/rust/src/tool.rs index 189bc6f21..ca2639dd0 100644 --- a/rust/src/tool.rs +++ b/rust/src/tool.rs @@ -174,6 +174,7 @@ pub fn convert_mcp_call_tool_result(value: &serde_json::Value) -> Option, + /// The tool count above which MCP and external tools are deferred behind + /// tool search. When unset, the runtime default (30) applies. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub defer_threshold: Option, +} + +impl ToolSearchConfig { + /// Construct an empty [`ToolSearchConfig`]; all fields default to unset + /// (the runtime applies its own defaults). + pub fn new() -> Self { + Self::default() + } + + /// Toggle that enables or disables tool search. + pub fn with_enabled(mut self, enabled: bool) -> Self { + self.enabled = Some(enabled); + self + } + + /// Set the tool count above which MCP and external tools are deferred + /// behind tool search. + pub fn with_defer_threshold(mut self, defer_threshold: u32) -> Self { + self.defer_threshold = Some(defer_threshold); + self + } +} + /// Configures infinite sessions: persistent workspaces with automatic /// context-window compaction. /// @@ -1674,6 +1713,10 @@ pub struct SessionConfig { pub plugin_directories: Option>, /// Configuration for large tool output handling, forwarded to the CLI. pub large_output: Option, + /// Overrides the runtime's built-in tool-search behavior, which defers + /// rarely used tools behind a searchable index. When unset, the runtime + /// default applies. + pub tool_search: Option, /// Skill names to disable. Skills in this set will not be available /// even if found in skill directories. pub disabled_skills: Option>, @@ -1878,6 +1921,7 @@ impl std::fmt::Debug for SessionConfig { .field("instruction_directories", &self.instruction_directories) .field("plugin_directories", &self.plugin_directories) .field("large_output", &self.large_output) + .field("tool_search", &self.tool_search) .field("disabled_skills", &self.disabled_skills) .field("hooks", &self.hooks) .field("custom_agents", &self.custom_agents) @@ -1987,6 +2031,7 @@ impl Default for SessionConfig { instruction_directories: None, plugin_directories: None, large_output: None, + tool_search: None, disabled_skills: None, hooks: None, custom_agents: None, @@ -2143,6 +2188,7 @@ impl SessionConfig { instruction_directories: self.instruction_directories, plugin_directories: self.plugin_directories, large_output: self.large_output, + tool_search: self.tool_search, disabled_skills: self.disabled_skills, custom_agents: self.custom_agents, default_agent: self.default_agent, @@ -2546,6 +2592,13 @@ impl SessionConfig { self } + /// Set the [`ToolSearchConfig`] overriding the runtime's built-in + /// tool-search behavior on session create. + pub fn with_tool_search(mut self, config: ToolSearchConfig) -> Self { + self.tool_search = Some(config); + self + } + /// Set the names of skills to disable (overrides skill discovery). pub fn with_disabled_skills(mut self, names: I) -> Self where @@ -2830,6 +2883,9 @@ pub struct ResumeSessionConfig { pub plugin_directories: Option>, /// Configuration for large tool output handling, forwarded to the CLI on resume. pub large_output: Option, + /// Overrides the runtime's built-in tool-search behavior on resume. When + /// unset, the runtime default applies. + pub tool_search: Option, /// Skill names to disable on resume. pub disabled_skills: Option>, /// Enable session hooks on resume. @@ -3002,6 +3058,7 @@ impl std::fmt::Debug for ResumeSessionConfig { .field("instruction_directories", &self.instruction_directories) .field("plugin_directories", &self.plugin_directories) .field("large_output", &self.large_output) + .field("tool_search", &self.tool_search) .field("disabled_skills", &self.disabled_skills) .field("hooks", &self.hooks) .field("custom_agents", &self.custom_agents) @@ -3155,6 +3212,7 @@ impl ResumeSessionConfig { instruction_directories: self.instruction_directories, plugin_directories: self.plugin_directories, large_output: self.large_output, + tool_search: self.tool_search, disabled_skills: self.disabled_skills, custom_agents: self.custom_agents, default_agent: self.default_agent, @@ -3242,6 +3300,7 @@ impl ResumeSessionConfig { instruction_directories: None, plugin_directories: None, large_output: None, + tool_search: None, disabled_skills: None, hooks: None, custom_agents: None, @@ -3625,6 +3684,13 @@ impl ResumeSessionConfig { self } + /// Set the [`ToolSearchConfig`] overriding the runtime's built-in + /// tool-search behavior on resume. + pub fn with_tool_search(mut self, config: ToolSearchConfig) -> Self { + self.tool_search = Some(config); + self + } + /// Set the names of skills to disable on resume. pub fn with_disabled_skills(mut self, names: I) -> Self where @@ -4826,6 +4892,9 @@ pub struct ToolResultExpanded { /// Tool-specific telemetry emitted with the result. #[serde(default, skip_serializing_if = "Option::is_none")] pub tool_telemetry: Option>, + /// Names of tools returned by a tool-search tool. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tool_references: Option>, } /// Result of a tool invocation — either a plain text string or an expanded result. @@ -5255,6 +5324,7 @@ mod tests { session_log: None, error: None, tool_telemetry: None, + tool_references: None, }), }; @@ -5289,6 +5359,7 @@ mod tests { session_log: None, error: None, tool_telemetry: None, + tool_references: None, }), }; diff --git a/rust/src/wire.rs b/rust/src/wire.rs index f73870fa5..b72e15758 100644 --- a/rust/src/wire.rs +++ b/rust/src/wire.rs @@ -27,7 +27,7 @@ use crate::types::{ CapiSessionOptions, CloudSessionOptions, CustomAgentConfig, DefaultAgentConfig, ExtensionInfo, InfiniteSessionConfig, LargeToolOutputConfig, McpServerConfig, MemoryConfiguration, NamedProviderConfig, ProviderConfig, ProviderModelConfig, SessionId, SessionLimitsConfig, - SystemMessageConfig, Tool, + SystemMessageConfig, Tool, ToolSearchConfig, }; /// Wire representation of a slash command (name + description only). The @@ -121,6 +121,8 @@ pub(crate) struct SessionCreateWire { #[serde(skip_serializing_if = "Option::is_none")] pub large_output: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub tool_search: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub disabled_skills: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub custom_agents: Option>, @@ -251,6 +253,8 @@ pub(crate) struct SessionResumeWire { #[serde(skip_serializing_if = "Option::is_none")] pub large_output: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub tool_search: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub disabled_skills: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub custom_agents: Option>, diff --git a/rust/tests/e2e/tool_results.rs b/rust/tests/e2e/tool_results.rs index a6047007f..abcf5373a 100644 --- a/rust/tests/e2e/tool_results.rs +++ b/rust/tests/e2e/tool_results.rs @@ -220,6 +220,7 @@ fn expanded(text: impl Into, result_type: impl Into) -> ToolResu session_log: None, error: None, tool_telemetry: None, + tool_references: None, }) }