# 在会话期间更改工具

应用程序可以替换它提供给实时会话的工具，而无需重新创建该实时会话。 例如，网页可以在用户导航时提供不同的工具，或者接管会话的客户端可以自带工具。 每个 SDK 都会通过一次调用替换客户端的完整工具集及为其提供服务的处理程序。

<!-- markdownlint-disable GHD046 GHD005 -->
<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->

> **实验性。** 替换工具会包装实验性`session.tools.set` RPC，并且需要一个支持它的运行时。

## 替换此客户的工具

传递此客户应提供的完整工具集。 它将替换客户端在创建或恢复会话时提供的工具，或在更早一次替换中提供的工具。 内置、MCP 和插件工具，以及连接到同一会话的其他客户端提供的工具，不受影响。 传递空列表以删除此客户端的所有工具。

工具的定义与创建会话完全相同，没有处理程序的工具的行为在创建会话时也是如此。

<div class="ghd-codetabs">
<div class="ghd-codetab" data-lang="typescript" data-label="TypeScript"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">TypeScript</div>

```typescript
import { defineTool } from "@github/copilot-sdk";
import { z } from "zod";

const searchIssues = defineTool("search_issues", {
    description: "Search the issues shown on the current page",
    parameters: z.object({ query: z.string().describe("Search text") }),
    handler: async ({ query }) => findIssues(query),
});

// Supply only the tools the current page provides.
await session.setTools([searchIssues]);

// Remove all of this client's tools.
await session.setTools([]);
```

</div>

<div class="ghd-codetab" data-lang="python" data-label="Python"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Python</div>

```python
from pydantic import BaseModel, Field
from copilot.tools import define_tool

class SearchIssuesParams(BaseModel):
    query: str = Field(description="Search text")

@define_tool(description="Search the issues shown on the current page")
async def search_issues(params: SearchIssuesParams) -> str:
    return await find_issues(params.query)

# Supply only the tools the current page provides.
await session.set_tools([search_issues])

# Remove all of this client's tools.
await session.set_tools([])
```

</div>

<div class="ghd-codetab" data-lang="go" data-label="Go"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Go</div>

```golang
type SearchIssuesParams struct {
    Query string `json:"query" jsonschema:"Search text"`
}

searchIssues := copilot.DefineTool(
    "search_issues",
    "Search the issues shown on the current page",
    func(params SearchIssuesParams, inv copilot.ToolInvocation) (string, error) {
        return findIssues(params.Query)
    },
)

// Supply only the tools the current page provides.
if err := session.SetTools(ctx, []copilot.Tool{searchIssues}); err != nil {
    log.Fatal(err)
}

// Remove all of this client's tools.
if err := session.SetTools(ctx, nil); err != nil {
    log.Fatal(err)
}
```

</div>

<div class="ghd-codetab" data-lang="dotnet" data-label=".NET"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">.NET</div>

```csharp
var searchIssues = CopilotTool.DefineTool(
    ([Description("Search text")] string query) => FindIssues(query),
    factoryOptions: new AIFunctionFactoryOptions
    {
        Name = "search_issues",
        Description = "Search the issues shown on the current page",
    });

// Supply only the tools the current page provides.
await session.SetToolsAsync([searchIssues]);

// Remove all of this client's tools.
await session.SetToolsAsync([]);
```

`SetToolsAsync`
`GHCP001`引发试验性诊断，可使用 `#pragma warning disable GHCP001` 或项目级别的 `<NoWarn>GHCP001</NoWarn>` 抑制该诊断。

</div>

<div class="ghd-codetab" data-lang="java" data-label="Java"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Java</div>

```java
var searchIssues = ToolDefinition.create(
    "search_issues",
    "Search the issues shown on the current page",
    Map.of(
        "type", "object",
        "properties", Map.of("query", Map.of("type", "string", "description", "Search text")),
        "required", List.of("query")),
    invocation -> CompletableFuture.completedFuture(
        findIssues((String) invocation.getArguments().get("query"))));

// Supply only the tools the current page provides.
session.setTools(List.of(searchIssues)).get();

// Remove all of this client's tools.
session.setTools(List.of()).get();
```

`setTools` 是 `@CopilotExperimental`，因此使用类或方法必须通过 `@AllowCopilotExperimental` 显式启用。 请参阅 [使用实验 API](https://github.com/github/copilot-sdk/tree/main/java/README.md#using-experimental-apis)。

</div>

<div class="ghd-codetab" data-lang="rust" data-label="Rust"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Rust</div>

<!-- docs-validate: skip -->

```rust
#[derive(Deserialize, JsonSchema)]
struct SearchIssuesParams {
    /// Search text
    query: String,
}

let search_issues = define_tool(
    "search_issues",
    "Search the issues shown on the current page",
    |_inv, params: SearchIssuesParams| async move {
        Ok(ToolResult::Text(find_issues(&params.query).await))
    },
);

// Supply only the tools the current page provides.
session.set_tools(vec![search_issues]).await?;

// Remove all of this client's tools.
session.set_tools(Vec::new()).await?;
```

</div>

</div>

## 新工具生效时

- **模型请求。** 运行时提供来自代理下一次模型请求的新工具。 该请求可以是正在进行的轮次的一部分。
- **处理程序。** 一旦运行时接受替换，会话调度的每次工具调用都会使用新的处理程序。 已在运行的调用将在启动它们的处理程序上完成。
- **拒绝。** 如果运行时拒绝替换，调用将失败，并且以前的工具和处理程序将保持不变。 运行时会拒绝名称中包含无效字符，或与另一已连接客户端已提供名称重复的工具。
- **订购。** 同一会话上的并发替换按顺序一次应用一个。
- **取消。** 在允许你取消该调用的 SDK 中，如果在较早的替换仍在进行时取消该调用，则不会发送任何内容。 发送请求后，取消只会停止等待：如果运行时接受替换，则新处理程序仍生效。

## 在轮次期间更换工具

已在进行中的模型请求是使用以前的工具发出的，因此代理仍然可以调用刚刚删除的工具。 会话不会响应该调用，因为另一个连接的客户端可能会提供具有相同名称的工具，因此该调用可以保持挂起状态，直到轮次中止。

如果正在运行的轮次仍可能调用将被删除的工具，则在会话处于空闲状态时替换工具，例如在 `session.idle` 事件之后，或先中止当前轮次。 在一次轮次中替换同名工具的处理程序是安全的：在运行时接受替换后调度的调用使用新处理程序。

## SDK 参考

| SDK | 方法 |
|---|---|
| TypeScript | `session.setTools(tools)` |
| Python | `await session.set_tools(tools)` |
| Go | `session.SetTools(ctx, tools)` |
| .NET | `await session.SetToolsAsync(tools, cancellationToken)` |
| Java | 
`session.setTools(tools)`，返回一个 `CompletableFuture<Void>` |
| Rust | `session.set_tools(tools).await` |