Skip to content

Commit 15bd00b

Browse files
author
SqlRush
committed
Preserve deferred tool request metadata
1 parent 63fb126 commit 15bd00b

7 files changed

Lines changed: 76 additions & 5 deletions

File tree

cmd/claude/main_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1997,7 +1997,7 @@ func TestRunPrintStreamJSONOutput(t *testing.T) {
19971997
if code != 0 {
19981998
t.Fatalf("exit = %d stderr=%s", code, stderr.String())
19991999
}
2000-
if betaHeader != "beta-one,beta-two,fast-mode-2025-01-24" {
2000+
if betaHeader != "beta-one,beta-two,fast-mode-2025-01-24,structured-outputs-2025-11-13" {
20012001
t.Fatalf("anthropic-beta = %q", betaHeader)
20022002
}
20032003
lines := strings.Split(strings.TrimSpace(stdout.String()), "\n")

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 搜索当前可用工具定义,返回 BM25-ranked structured results 和 read-only/concurrency/destructive 元数据,并兼容 `query`/`q`/`search``topn`/`limit` 等输入别名。完整 deferred/lazy tool discovery 仍未完成。
195195

196+
M8/M2 补充:Anthropic request tool 转换现在会保留 contract 的 `strict``should_defer`,将 deferred 工具序列化为 API `defer_loading`,并用 `always_load` 覆盖 deferred hint;conversation runner 构造请求时会把 `Task` 等 deferred tool 的 strict/defer_loading 元数据带到最终请求。完整 deferred/lazy tool discovery 仍未完成。
197+
196198
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 仍未完成。
197199

198200
M8 补充:本地 prompt skill 的 slash 调用和 `Skill` tool 现在都会生成 `command_permissions` attachment,按官方 `allowed-tools` 解析 comma/space 分隔且保留括号内模式;conversation runner 会在当前 turn 内把这些 `PermissionSourceCommand` allow rules 合并进 engine permission decider,让 skill frontmatter 授权的后续工具调用可在同一轮放行,并继续保留 model override attachment metadata。完整权限 UI 展示、SDK event surface、forked/MCP/plugin/bundled skill 权限继承仍未完成。

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 搜索当前工具定义,返回 BM25-ranked structured results 和 read-only/concurrency/destructive 元数据,并兼容 query/limit 相邻输入别名。deferred/lazy tool discovery 仍未宣称完成。
294+
- 本轮补充:Anthropic request tool 转换会保留 contract 的 `strict``should_defer`,把 deferred 工具序列化为 API `defer_loading`,并让 `always_load` 覆盖 deferred hint;conversation runner 构造请求时会保留 `Task` 等 deferred tool 的 strict/defer_loading 元数据。完整 deferred/lazy tool discovery 仍未宣称完成。
294295
- 本轮补充:新增基础 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 仍未宣称完成。
295296
- 本轮补充:本地 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 权限继承仍未宣称完成。
296297
- 本轮补充:skill frontmatter 标量兼容继续补齐,`allowed_tools`/`argument_hint`/`disable_model_invocation`/`user_invocable`/`when-to-use` 等相邻字段会映射到 canonical command metadata;`model: inherit` 不再误触发模型覆盖,`context: fork``agent``effort` 会保留在 command contract 中,为后续 forked skill/agent 执行接线提供 metadata;当 policy 锁定 `agents` surface 时,非可信来源 prompt command 的这些 agent metadata 会在 registry 层清除,plugin/bundled/admin 来源保留。

docs/first-second-parity-audit.md

Lines changed: 2 additions & 1 deletion
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, and search hint, returning BM25-ranked structured results with read-only/concurrency/destructive metadata and query/limit aliases.
92+
- Anthropic request tool conversion now preserves contract-level `strict` and API-level `defer_loading` hints from deferred tool definitions, with `always_load` overriding `should_defer`; runner request construction preserves these fields for tools such as `Task`.
9293
- 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.
9394
- 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.
9495
- Settings schema coverage was expanded for major Claude Code settings keys: auth helpers, model allowlists/overrides, MCP policy, hooks policy, worktree, shell, output style, language, thinking/effort, plugins, remote, spinner, sandbox, and related flags. Settings parsing now also coerces `env` values to strings, filters invalid permission rules into warnings, validates key permission fields, applies WebSearch/WebFetch-specific permission-rule validation, validates marketplace source union basics across `extraKnownMarketplaces`, `strictKnownMarketplaces`, and `blockedMarketplaces` including settings-source map key/source name consistency, plugin item path shape, and `installLocation` user/project/local values, derives runtime marketplace allow/block decisions from strict and blocked settings, loads installed plugins from project-chain and user plugin roots with project precedence, loads marketplace-only plugin listings for headless `/plugin available` and `/plugin marketplace plugins|search|show` while preserving installed plugin available/update state, exposes installed/available marketplace JSON through CLI `plugin list --json --available`, exposes configured marketplace list JSON/text through CLI `plugin marketplace list`, writes and removes configured marketplaces to user/project/local settings scopes through CLI `plugin marketplace add/remove --scope ...`, triggers marketplace cache refresh through CLI `plugin marketplace update [name]`, shares marketplace install copy/conflict/symlink-safety code between headless `/plugin install [--scope project|user|local]` and CLI `plugin install --scope project|user|local` with marketplace `installLocation` defaults, shares marketplace update/replace code between headless `/plugin update [--scope project|user|local|all] [name]` and CLI `plugin update --scope project|user|local|all <plugin>` with the same named-plugin defaulting, writes plugin enable/disable state to user/project/local settings scopes through CLI `plugin enable|disable --scope ...`, loads managed policy from macOS MDM plist / Windows HKLM, platform `managed-settings.json`, sorted `managed-settings.d/*.json` drop-ins, optional remote managed-settings GET sources, and Windows HKCU fallback, refreshes remote managed policy at turn start and during daemon heartbeat ticks with merged-settings/plugin-server app-state propagation, honors `allowManagedPermissionRulesOnly` for permission-rule merging, enforces `strictPluginOnlyCustomization` for the implemented `skills`, `mcp`, and `hooks` surfaces so user/project/local customizations are skipped while plugin/admin sources remain, strips untrusted prompt-command `agent`/`context: fork`/`effort` metadata when the `agents` surface is policy-locked while preserving plugin/bundled/admin sources, includes a path-keyed settings file cache plus a snapshot-based settings change detector that classifies created/modified/deleted settings files and resets cached reads on change, refreshes local user/project/local settings at turn start with plugin-server app-state propagation, and now generates a JSON Schema from `contracts.Settings` with `/config show schema` summary visibility.
@@ -107,7 +108,7 @@ The following items remain incomplete and must not be treated as done:
107108
- Auto mode / YOLO classifier: transcript construction, two-stage classifier, XML/tool-use parsing, prompt dump, denial circuit breaker, model gating, and fallback behavior.
108109
- Interactive permission prompt flow: REPL dialogs, bridge/channel/swarm permission relays, user feedback images, prompt race handling, cancellation.
109110
- Full filesystem permission parity gaps that remain: skill-scope allow suggestions, remaining local/local-jsx slash-command execution wiring beyond the currently covered no-query builtins, command permission UI/SDK surfacing, forked/remote/MCP/bundled SkillTool behavior and remaining plugin SkillTool UI/SDK polish beyond current metadata preservation, skill prompt shell injection, complete auto-memory override policy, and deeper platform-specific Windows/WSL bypass handling.
110-
- Full tool execution parity gaps that remain: complete hook runtime policy beyond the current settings command hook path, MCP elicitation, complete SDK control event surface beyond current progress NDJSON, deferred/lazy tool discovery beyond the current local registry BM25 search, mid-call cancellation for concrete tools, background task behavior, telemetry, schema-not-sent hints, and concrete tool-specific semantics.
111+
- Full tool execution parity gaps that remain: complete hook runtime policy beyond the current settings command hook path, MCP elicitation, complete SDK control event surface beyond current progress NDJSON, full deferred/lazy tool discovery beyond current local registry BM25 search and API `defer_loading` hints, mid-call cancellation for concrete tools, background task behavior, telemetry, schema-not-sent hints, and concrete tool-specific semantics.
111112
- Complete Anthropic API parity gaps that remain: dynamic beta-header latching for any remaining provider-specific feature gates beyond prompt-cache/cache-editing/strict-output/1M-context requests, ant-only dump gating and remote `/issue` submission integration beyond the current local context summary, full official cost tracker/session restore lifecycle beyond current transcript usage restore, post-handshake streaming failure semantics, deeper gateway/proxy auth and routing behavior beyond static custom headers, first-party/Bedrock/Vertex/Foundry client setup, deeper OAuth account/profile lifecycle beyond request-time refresh retry, fast-mode retry/cooldown semantics, persistent unattended retry heartbeats, full prompt-cache editing lifecycle, and provider-specific cache behavior.
112113
- Full conversation/query loop: stop hooks, compact/auto-compact, token budget escalation, resume, SDK JSON/NDJSON control events, status updates, rate-limit handling, model switch breadcrumbs, side questions.
113114
- Full settings parity gaps that remain: complete Zod-equivalent validation messages, full remote managed-settings watcher/non-daemon background refresh lifecycle beyond current turn-start and daemon-heartbeat refresh, remaining forked agent/frontmatter execution and UI edge cases beyond current policy metadata filtering, complete marketplace TUI/UI/background lifecycle beyond current local/settings/directory/file/URL catalog/git/github/npm cache manifest load, URL catalog cache fallback, git/github fetch/pull cache refresh, npm pack cache refresh, and background watcher/continuous app-state sync beyond current turn-start local settings reload.

internal/api/anthropic/types.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ type ToolDefinition struct {
3636

3737
func ToolFromContract(def contracts.ToolDefinition) ToolDefinition {
3838
return ToolDefinition{
39-
Name: def.Name,
40-
Description: def.Description,
41-
InputSchema: def.InputSchema,
39+
Name: def.Name,
40+
Description: def.Description,
41+
InputSchema: def.InputSchema,
42+
Strict: def.Strict,
43+
DeferLoading: def.ShouldDefer && !def.AlwaysLoad,
4244
}
4345
}
4446

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package anthropic
2+
3+
import (
4+
"testing"
5+
6+
"ccgo/internal/contracts"
7+
)
8+
9+
func TestToolFromContractPreservesStrictAndDeferLoading(t *testing.T) {
10+
got := ToolFromContract(contracts.ToolDefinition{
11+
Name: "Task",
12+
Description: "Start a task",
13+
InputSchema: contracts.JSONSchema{
14+
"type": "object",
15+
},
16+
Strict: true,
17+
ShouldDefer: true,
18+
})
19+
if got.Name != "Task" || got.Description != "Start a task" || got.InputSchema["type"] != "object" {
20+
t.Fatalf("tool = %#v", got)
21+
}
22+
if !got.Strict {
23+
t.Fatalf("strict = false, want true")
24+
}
25+
if !got.DeferLoading {
26+
t.Fatalf("defer loading = false, want true")
27+
}
28+
}
29+
30+
func TestToolFromContractAlwaysLoadOverridesShouldDefer(t *testing.T) {
31+
got := ToolFromContract(contracts.ToolDefinition{
32+
Name: "Task",
33+
InputSchema: contracts.JSONSchema{"type": "object"},
34+
ShouldDefer: true,
35+
AlwaysLoad: true,
36+
})
37+
if got.DeferLoading {
38+
t.Fatalf("defer loading = true, want false when always_load is set")
39+
}
40+
}

internal/conversation/run_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7818,6 +7818,31 @@ func TestBuildRequestIncludesToolDefinitions(t *testing.T) {
78187818
}
78197819
}
78207820

7821+
func TestBuildRequestPreservesDeferredToolMetadata(t *testing.T) {
7822+
registry, err := tool.NewRegistry(tasktools.NewTaskTool())
7823+
if err != nil {
7824+
t.Fatal(err)
7825+
}
7826+
runner := Runner{
7827+
Tools: tool.NewExecutor(registry),
7828+
Model: "sonnet",
7829+
MaxTokens: 100,
7830+
}
7831+
req, err := runner.BuildRequest([]contracts.Message{messages.UserText("hi")}, "sonnet")
7832+
if err != nil {
7833+
t.Fatal(err)
7834+
}
7835+
if len(req.Tools) != 1 || req.Tools[0].Name != "Task" {
7836+
t.Fatalf("tools = %#v", req.Tools)
7837+
}
7838+
if !req.Tools[0].DeferLoading {
7839+
t.Fatalf("task defer_loading = false, want true")
7840+
}
7841+
if !req.Tools[0].Strict {
7842+
t.Fatalf("task strict = false, want true")
7843+
}
7844+
}
7845+
78217846
func TestRunnerGatesAdvancedLSPDiagnosticsTool(t *testing.T) {
78227847
requestForAdvancedSetting := func(t *testing.T, advanced *contracts.AdvancedSetting) anthropic.Request {
78237848
t.Helper()

0 commit comments

Comments
 (0)