github / github/copilot-sdk

Feature Request: mutate the active tool list on a live session (SDK re-triage of #735)

未关闭
#1,896 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
enhancement
主要语言
Java
星标
10.5k
派生
1.5k
平均合并
1 天 14 小时
30 天内合并 PR
129

描述

# Feature Request: mutate the active tool list on a live session (SDK re-triage of #735)

Refiling per @patniko's invitation in [#735](https://github.com/github/copilot-sdk/issues/735#issuecomment-4626625863) — the original closing comment specifically asked for fresh reproduction details from a current SDK user, and this is exactly that.

## Problem

`CopilotSession` today takes its custom tool list once at creation (`client.createSession({ tools })`) and again at `client.resumeSession({ tools })`. There is no supported way to mutate the active tool list on a **live session** without terminating conversation state at the SDK layer.

This blocks a well-defined UX pattern that MCP-integrating hosts consistently hit: **slow-auth MCP servers that connect after session creation**.

## Concrete production repro

**Host**: [`obsidian-copilot-agent`](https://github.com/ChrisKrawczyk/obsidian-copilot-agent) — an Obsidian community plugin embedding Copilot Agent, shipped v0.8.0.

**Scenario** (verified end-to-end):

1. User opens Obsidian → plugin loads → the plugin's `ConversationManager` creates a `CopilotSession` for the active conversation. Tool list at this point = built-ins + already-connected MCP servers only.
2. User has an MCP server configured that uses Device Flow / OAuth (Microsoft Graph via WorkIQ is the concrete case, but any slow-auth server exhibits this). This server has not finished authenticating yet.
3. 10–90 seconds later, the MCP server completes auth and connects. The plugin's `McpManager` fires a connect notification. New tools are discovered.
4. **Expected**: the user, mid-conversation, can ask the assistant to use the newly available tools.
5. **Actual**: the assistant reports it has no such tools. The tool list frozen at `createSession(...)` time is still what the CLI/model sees.
6. **Only workaround**: reload the plugin (loses the conversation) or switch conversations away and back (metadata-only conversations pick up the fresh tool list on their next `createSession(...)`, at the cost of a session recreation).

Same pattern shows up with:
- MCP servers that restart / crashloop (their tools should drop out for the duration).
- MCP servers added post-hoc via the settings UI while a conversation is open.
- Tool-schema changes on an already-connected server (`session.tools_updated` fires but the plugin has no way to act on it).

## Related prior issues

- **[#735](https://github.com/github/copilot-sdk/issues/735)** — *setActiveTools() — filter visible tools without session teardown.* Same underlying feature; closed as backlog trim, invited re-file.
- **[#1143](https://github.com/github/copilot-sdk/issues/1143)** — *session-scoped tool inventory API.* Adjacent read-side gap (list what's actually loaded); explicitly cross-references #735 as the write-side counterpart.
- **[#861](https://github.com/github/copilot-sdk/issues/861)** / **[#869](https://github.com/github/copilot-sdk/issues/869)** — MCP tool naming issues that hosts already work around, but the underlying wiring is the same code path a live-update method would touch.

## What we already did to isolate the SDK gap

To be sure this is an SDK gap and not a host bug, `obsidian-copilot-agent` v0.8.0 landed a full readiness/refresh flow *inside the plugin*:

- Watches `McpManager` state transitions with per-server 5s coalescing (proposal 0003 in-repo).
- Attempts to refresh the live session's tools via a feature-detect: `typeof session.updateTools === "function"`. With SDK `1.0.0` this always returns false, so we log a strict no-op ([FR-011 in our spec](https://github.com/ChrisKrawczyk/obsidian-copilot-agent/blob/main/.paw/work/mcp-readiness-ux/Spec.md)) and surface a "server connected" toast only when we can actually act.
- Surfaces an inline chat composer readiness pill so users at least know MCP is still coming up.

We deliberately did not attempt `resumeSession(...)` as a workaround because:
1. It creates a fresh session object; every event subscription (`session.on`, `session.onPermissionRequest`, canvas handlers, elicitation, tool handlers, hooks) has to be reattached. This is exactly the fragility #735 warned about — the Node SDK exhibits the same class of issue as the Python SDK originally reported, just with different specifics.
2. There is no documented guarantee that `resumeSession(sessionId, { tools })` preserves in-flight turn state or event ordering if called during `sendAndWait`.
3. Even at idle, doing a whole-object swap on every MCP transition is a lot of blast radius for what should be an in-place mutation.

So we hit the wall and shipped behind the no-op fallback.

## Requested API

Not prescriptive on shape — a maintainer decision — but a minimum viable surface would be one of:

**Option A** (mirrors #735 as originally proposed):
```ts
session.setActiveTools(names: string[]): Promise;
```
Filters *previously registered* tools by name for the next-turn context window. Simple, low-blast-radius, but requires the caller to register the full palette up front.

**Option B** (mirrors the existing `setModel` pattern for mid-session mutation):
```ts
session.updateTools(tools: Tool[]): Promise;
```
Replaces the declared list wholesale. More flexible for MCP integration where the tool set genuinely changes (server connect adds new tool *names*, not just a filter over a pre-known palette).

For MCP-integrating hosts, **B** is strictly more useful because we don't know the tool names ahead of time — they come from the MCP server's `tools/list` response, which the CLI executes internally.

### Semantics we would rely on (for either option)

- Effect timing: **next user turn onward**. In-flight turn's tool declarations are frozen — mid-stream calls do not affect them. This matches the plugin's existing turn-boundary latch and is the least-surprising behavior for the model.
- Ordering under concurrency: **last-committed wins**. Callers cannot reasonably serialize; the host already has to deal with rapid MCP flapping.
- Disconnected session: **rejects** with a documented error type. Do not silently no-op — hosts need to distinguish "SDK doesn't support this" from "session is dead".
- Conversation state: preserved (history, canvases, permission handlers, event subscribers). This is the entire point vs. `resumeSession`.
- Handler registration (Option B only): tool handlers registered via `defineTool` and passed in the new list should be dispatched by name on subsequent turns.

## Contribution offer

Happy to author the Node SDK side of the PR once the API shape is decided — the plugin-side integration is already coded behind a feature detect, so adoption is a version bump + swap-in.

## Environment

- `@github/copilot-sdk@1.0.0` (Node)
- Obsidian community plugin, Electron renderer process, `requestUrl`-routed MCP HTTP
- Windows / macOS / Linux verified

/cc @patniko (per #735 close-comment invitation)

贡献指南

打开贡献指南

调研方向

从 Node SDK 的 CopilotSession createSession 和 resumeSession 路径入手,然后将它们与现有的 setModel mutation 和 defineTool handler registration 进行比较。跟踪工具声明如何跨越 turn 边界,以及 session 状态和订阅如何被保留;完成的标准是一个经维护者批准、并对并发、断开连接的 session 以及 handler 语义作出文档说明的 live-update API。

由索引模型根据 Issue 内容生成。

评估

技术栈
node.js, typescript
领域
api, tooling
Issue 类型
功能
难度
5/5
预计耗时
一周以上
活跃度
冷清
描述清晰度
基本清楚
新手友好度
45/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。