Skip to content

Commit f12eae5

Browse files
author
SqlRush
committed
Load discovered tool references
1 parent cabd4fc commit f12eae5

5 files changed

Lines changed: 159 additions & 0 deletions

File tree

docs/cc-100-roadmap.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ M8 补充:新增基础 `Skill` tool wrapper,已注册到默认内置工具
193193

194194
M8 补充:新增基础 `ToolSearch` tool 并注册到默认内置工具集,executor 会把当前 tool registry 放入工具 metadata,`ToolSearch` 可按 name、alias、description、prompt、search hint 和 input/output schema 字段搜索当前可用工具定义,支持 `select:ToolA,ToolB` 直接选择并返回 `tool_reference` content,返回 BM25/select structured results、read-only/concurrency/destructive、input/output schema 以及 `should_defer`/`always_load`/`requires_interaction`/`strict`/cache/eager 等请求提示元数据,并兼容 `query`/`q`/`search``topn`/`limit` 等输入别名。完整 deferred/lazy tool discovery 仍未完成。
195195

196+
M8/M2 补充:conversation `BuildRequest` 现在会扫描历史 `tool_result.content` 中的 `tool_reference`,并把已发现工具在后续 API request 中作为 loaded tool 发送,不再携带 `defer_loading`;扫描兼容运行时 `ToolReference` 值和 transcript/JSON 解码后的 map 形态。完整官方 tool-reference expansion/filtering 仍未完成。
197+
196198
M8/M2 补充:Anthropic request tool 转换现在会保留 contract 的 `strict``eager_input_streaming``cache_control``should_defer`,将 deferred 工具序列化为 API `defer_loading`,并用 `always_load` 覆盖 deferred hint;API tool description 会按 description、prompt、searchHint 顺序 fallback,conversation runner 构造请求时会把 `Task` 等 deferred tool 的 strict/defer_loading 元数据带到最终请求。完整 deferred/lazy tool discovery 仍未完成。
197199

198200
M8 补充:新增基础 slash command parser/executor,按官方 `/command args``/mcp:tool (MCP) args` 形态解析,并把本地项目 prompt skill slash 调用接入 conversation runner:`/skill args` 会生成 `<command-name>/<command-message>/<command-args>` metadata user message 和展开后的 meta prompt message,写入 transcript/parent chain 后再请求模型;skill frontmatter `model` 可覆盖本轮请求模型。local/local-jsx 命令目前只返回未实现输出,不会误发给模型;command permissions attachment、forked skill、MCP/plugin/bundled slash 来源和 UI 仍未完成。

docs/claude-code-go-rewrite-plan.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ test/parity/ # golden tests against TS/official behavior
291291
- 本轮补充:command registry 现在保存本地 skill prompt template,并提供 prompt expansion 入口,覆盖 `$ARGUMENTS`、indexed/shorthand 参数、frontmatter named arguments、`${CLAUDE_SESSION_ID}` 替换、非 MCP skill 的 `${CLAUDE_SKILL_DIR}` 替换和 meta user message 输出;shell injection、SkillTool wrapper、local/local-jsx 执行和 REPL/UI wiring 仍未宣称完成。
292292
- 本轮补充:新增基础 `Skill` tool wrapper 并注册到默认内置工具集,可调用本地项目 prompt skill、兼容官方 `skill`/`args` 与相邻别名,并把 prompt expansion 产生的 meta user message 通过 `ToolResult.NewMessages` 交给 conversation runner;runner 现在会把这些新消息写入 transcript 并追加到下一轮模型请求。插件目录 skill 可通过工作目录发现和 display name 调用,Skill tool structured content 会保留 source、loadedFrom、displayName、description、argument metadata、skillRoot、whenToUse、version、context、agent、effort、paths、contentLength 和 progressMessage。forked/remote/MCP/bundled skills、plugin skill UI/SDK polish、shell injection、slash/local command UI wiring 仍未宣称完成。
293293
- 本轮补充:新增基础 `ToolSearch` tool 并注册到默认内置工具集,executor 会把当前 tool registry 注入工具 metadata;`ToolSearch` 可按 name、alias、description、prompt、search hint 和 input/output schema 字段搜索当前工具定义,支持 `select:ToolA,ToolB` 直接选择并返回 `tool_reference` content,返回 BM25/select structured results 和 read-only/concurrency/destructive、input/output schema 以及 `should_defer`/`always_load`/`requires_interaction`/`strict`/cache/eager 等请求提示元数据,并兼容 query/limit 相邻输入别名。deferred/lazy tool discovery 仍未宣称完成。
294+
- 本轮补充:conversation `BuildRequest` 会扫描历史 `tool_result.content` 中的 `tool_reference`,并把已发现工具在后续 API request 中作为 loaded tool 发送,不再携带 `defer_loading`;扫描兼容运行时 `ToolReference` 值和 transcript/JSON 解码后的 map 形态。完整官方 tool-reference expansion/filtering 仍未宣称完成。
294295
- 本轮补充:Anthropic request tool 转换会保留 contract 的 `strict``eager_input_streaming``cache_control``should_defer`,把 deferred 工具序列化为 API `defer_loading`,并让 `always_load` 覆盖 deferred hint;API tool description 会按 description、prompt、searchHint 顺序 fallback,conversation runner 构造请求时会保留 `Task` 等 deferred tool 的 strict/defer_loading 元数据。完整 deferred/lazy tool discovery 仍未宣称完成。
295296
- 本轮补充:新增基础 slash command parser/executor,支持官方 `/command args``/mcp:tool (MCP) args` 解析;conversation runner 现在会在请求模型前展开本地项目 prompt skill slash command,生成 command metadata user message 和 meta prompt message,保留 transcript parent chain,并支持 skill `model` 覆盖本轮请求。local/local-jsx 目前只返回未实现输出且不会误发模型,command permissions attachment、forked/MCP/plugin/bundled slash command 和 UI wiring 仍未宣称完成。
296297
- 本轮补充:本地 prompt skill slash command 和 `Skill` tool 现在都会生成 `command_permissions` attachment,解析 `allowed-tools` 的 comma/space 分隔形式并保留括号内 tool pattern;Runner 会在当前 turn 内把 attachment 转成 `PermissionSourceCommand` allow rules 合并进 engine permission decider,让 skill 授权的后续工具调用可在同一轮通过。完整权限 UI/SDK 展示、forked/MCP/plugin/bundled skill 权限继承仍未宣称完成。

docs/first-second-parity-audit.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ Anthropic API 和 conversation:
8989
- `/model show`, `/model info`, and `/model current` now take the no-query current-model path instead of treating those words as custom model names and mutating the runner model.
9090
- A basic `Skill` tool wrapper is now registered with the default built-in tool set. It can invoke local project and plugin-directory prompt skills through the command registry, returns the official-style `Launching skill: ...` result plus structured command metadata, preserves command source/display/root/frontmatter metadata for plugin skills, and passes expanded meta user messages through `ToolResult.NewMessages`; the conversation runner now appends those new messages to transcripts and subsequent model requests.
9191
- A basic `ToolSearch` tool is now registered with the default built-in tool set. The executor exposes the current registry through tool metadata, and `ToolSearch` can search available definitions by name, alias, description, prompt, search hint, and input/output schema fields, supports `select:ToolA,ToolB` direct selection, and returns tool-reference content plus BM25/select structured results with read-only/concurrency/destructive, input/output schema, deferred/loading/request-hint metadata, and query/limit aliases.
92+
- Conversation request construction now scans previous `tool_result` blocks for `tool_reference` content from `ToolSearch`; referenced tools are copied into the next API request without `defer_loading`, including both in-memory `ToolReference` values and decoded transcript map forms.
9293
- Anthropic request tool conversion now preserves contract-level `strict`, `eager_input_streaming`, `cache_control`, and API-level `defer_loading` hints from deferred tool definitions, with `always_load` overriding `should_defer`; it also falls back from `description` to `prompt` to `searchHint` when building API tool descriptions. Runner request construction preserves these fields for tools such as `Task`.
9394
- Basic slash prompt command invocation is now wired for local project prompt skills: `/skill args` is parsed before the model request, converted into official-shaped command metadata plus expanded meta prompt messages, appended to transcript parent chains, and can override the turn model from skill frontmatter.
9495
- Local prompt-skill slash commands and the `Skill` tool now emit `command_permissions` attachments, parse `allowed-tools` into command-scoped permission rules, and merge those rules into the engine permission decider for the current turn so authorized follow-up tool calls can pass without leaking into later turns.

internal/conversation/request.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package conversation
22

33
import (
4+
"strings"
45
"time"
56

67
"ccgo/internal/api/anthropic"
@@ -46,13 +47,98 @@ func (r Runner) buildRequest(history []contracts.Message, model string, relevant
4647
if err != nil {
4748
return anthropic.Request{}, err
4849
}
50+
definitions = applyDiscoveredToolReferences(definitions, history)
4951
if len(definitions) > 0 {
5052
request.Tools = anthropic.ToolsFromContracts(definitions)
5153
}
5254
}
5355
return request, nil
5456
}
5557

58+
func applyDiscoveredToolReferences(definitions []contracts.ToolDefinition, history []contracts.Message) []contracts.ToolDefinition {
59+
discovered := discoveredToolReferenceNames(history)
60+
if len(discovered) == 0 || len(definitions) == 0 {
61+
return definitions
62+
}
63+
out := make([]contracts.ToolDefinition, len(definitions))
64+
copy(out, definitions)
65+
for i := range out {
66+
if toolDefinitionDiscovered(out[i], discovered) {
67+
out[i].AlwaysLoad = true
68+
out[i].ShouldDefer = false
69+
}
70+
}
71+
return out
72+
}
73+
74+
func toolDefinitionDiscovered(definition contracts.ToolDefinition, discovered map[string]struct{}) bool {
75+
if _, ok := discovered[strings.ToLower(definition.Name)]; ok {
76+
return true
77+
}
78+
for _, alias := range definition.Aliases {
79+
if _, ok := discovered[strings.ToLower(alias)]; ok {
80+
return true
81+
}
82+
}
83+
return false
84+
}
85+
86+
func discoveredToolReferenceNames(history []contracts.Message) map[string]struct{} {
87+
discovered := map[string]struct{}{}
88+
for _, message := range history {
89+
for _, block := range message.Content {
90+
if block.Type != contracts.ContentToolResult {
91+
continue
92+
}
93+
collectToolReferenceNames(block.Content, discovered)
94+
}
95+
}
96+
return discovered
97+
}
98+
99+
func collectToolReferenceNames(content any, discovered map[string]struct{}) {
100+
switch typed := content.(type) {
101+
case contracts.ToolReference:
102+
addDiscoveredToolReference(typed.ToolName, discovered)
103+
case []contracts.ToolReference:
104+
for _, reference := range typed {
105+
addDiscoveredToolReference(reference.ToolName, discovered)
106+
}
107+
case map[string]any:
108+
if toolName, ok := stringMapField(typed, "tool_name", "toolName", "name"); ok && toolReferenceType(typed) {
109+
addDiscoveredToolReference(toolName, discovered)
110+
}
111+
case []map[string]any:
112+
for _, item := range typed {
113+
collectToolReferenceNames(item, discovered)
114+
}
115+
case []any:
116+
for _, item := range typed {
117+
collectToolReferenceNames(item, discovered)
118+
}
119+
}
120+
}
121+
122+
func toolReferenceType(item map[string]any) bool {
123+
value, ok := stringMapField(item, "type")
124+
return ok && value == "tool_reference"
125+
}
126+
127+
func stringMapField(item map[string]any, names ...string) (string, bool) {
128+
for _, name := range names {
129+
if value, ok := item[name].(string); ok && strings.TrimSpace(value) != "" {
130+
return strings.TrimSpace(value), true
131+
}
132+
}
133+
return "", false
134+
}
135+
136+
func addDiscoveredToolReference(toolName string, discovered map[string]struct{}) {
137+
if trimmed := strings.TrimSpace(toolName); trimmed != "" {
138+
discovered[strings.ToLower(trimmed)] = struct{}{}
139+
}
140+
}
141+
56142
func appendRelevantMemoryPrefetch(history []contracts.Message, result memory.RelevantMemoryPrefetchResult) []contracts.Message {
57143
if len(result.Memories) == 0 {
58144
return history

internal/conversation/run_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7912,6 +7912,75 @@ func TestBuildRequestPreservesDeferredToolMetadata(t *testing.T) {
79127912
}
79137913
}
79147914

7915+
func TestBuildRequestLoadsDiscoveredToolReferences(t *testing.T) {
7916+
registry, err := tool.NewRegistry(tasktools.NewTaskTool())
7917+
if err != nil {
7918+
t.Fatal(err)
7919+
}
7920+
runner := Runner{
7921+
Tools: tool.NewExecutor(registry),
7922+
Model: "sonnet",
7923+
MaxTokens: 100,
7924+
}
7925+
history := []contracts.Message{
7926+
messages.UserText("find task"),
7927+
{
7928+
Type: contracts.MessageUser,
7929+
Content: []contracts.ContentBlock{{
7930+
Type: contracts.ContentToolResult,
7931+
ToolUseID: "toolu_search",
7932+
Content: []contracts.ToolReference{
7933+
contracts.NewToolReference("Task"),
7934+
},
7935+
}},
7936+
},
7937+
}
7938+
req, err := runner.BuildRequest(history, "sonnet")
7939+
if err != nil {
7940+
t.Fatal(err)
7941+
}
7942+
if len(req.Tools) != 1 || req.Tools[0].Name != "Task" {
7943+
t.Fatalf("tools = %#v", req.Tools)
7944+
}
7945+
if req.Tools[0].DeferLoading {
7946+
t.Fatalf("task defer_loading = true after tool_reference discovery")
7947+
}
7948+
}
7949+
7950+
func TestBuildRequestLoadsDecodedToolReferenceMaps(t *testing.T) {
7951+
registry, err := tool.NewRegistry(tasktools.NewTaskTool())
7952+
if err != nil {
7953+
t.Fatal(err)
7954+
}
7955+
runner := Runner{
7956+
Tools: tool.NewExecutor(registry),
7957+
Model: "sonnet",
7958+
MaxTokens: 100,
7959+
}
7960+
history := []contracts.Message{
7961+
{
7962+
Type: contracts.MessageUser,
7963+
Content: []contracts.ContentBlock{{
7964+
Type: contracts.ContentToolResult,
7965+
ToolUseID: "toolu_search",
7966+
Content: []any{
7967+
map[string]any{"type": "tool_reference", "tool_name": "Task"},
7968+
},
7969+
}},
7970+
},
7971+
}
7972+
req, err := runner.BuildRequest(history, "sonnet")
7973+
if err != nil {
7974+
t.Fatal(err)
7975+
}
7976+
if len(req.Tools) != 1 || req.Tools[0].Name != "Task" {
7977+
t.Fatalf("tools = %#v", req.Tools)
7978+
}
7979+
if req.Tools[0].DeferLoading {
7980+
t.Fatalf("task defer_loading = true after decoded tool_reference discovery")
7981+
}
7982+
}
7983+
79157984
func TestRunnerGatesAdvancedLSPDiagnosticsTool(t *testing.T) {
79167985
requestForAdvancedSetting := func(t *testing.T, advanced *contracts.AdvancedSetting) anthropic.Request {
79177986
t.Helper()

0 commit comments

Comments
 (0)