Skip to content

feat(agent): tool call prefix stripping for proxy providers#259

Merged
viettranx merged 3 commits into
nextlevelbuilder:mainfrom
xuandung38:feat/tool-call-prefix-stripping
Mar 20, 2026
Merged

feat(agent): tool call prefix stripping for proxy providers#259
viettranx merged 3 commits into
nextlevelbuilder:mainfrom
xuandung38:feat/tool-call-prefix-stripping

Conversation

@xuandung38
Copy link
Copy Markdown
Contributor

@xuandung38 xuandung38 commented Mar 18, 2026

Summary

Proxy LLM providers like LiteLLM and OpenRouter may prepend a configurable prefix to tool call names returned by the model. For example, if the prefix template is proxy_{tool_name}, the model returns proxy_exec instead of exec.

This broke multiple systems:

  • Tool policy validationproxy_exec not in the allow list → blocked
  • Registry lookup — no tool registered as proxy_exec → execution failed
  • Hardcoded name checksteam_tasks and spawn detection in serial/parallel paths failed when prefixed

Changes

  • StripToolPrefix() in internal/tools/policy.go

    • Supports literal prefix (proxy_) and template pattern (proxy_{tool_name})
    • Template extracts the actual tool name using prefix/suffix matching
  • resolveToolCallName() in internal/agent/loop.go

    • Called before permission checks, registry execution, and spawn/team_tasks detection
    • Uses registryName (stripped) for all downstream operations while preserving original tc.Name in logs/spans
    • Applied in both serial and parallel tool execution paths
  • Agent config persistence (internal/store/agent_store.go)

    • ParseToolCallPrefix() reads from tool_policy_config.toolCallPrefix
    • Backward compatibility with legacy toolPrefix JSON key
  • Frontend (ui/web)

    • Text input in Tool Policy section of agent Config tab
    • Placeholder shows template format: prefix_{tool_name}
    • i18n support for EN, VI, ZH
    • Config save uses spread operator to preserve new fields

Files Changed

File Change
internal/agent/loop.go Add resolveToolCallName(), use registryName in serial + parallel paths
internal/config/config_channels.go Add ToolCallPrefix to ToolPolicySpec
internal/store/agent_store.go Add ParseToolCallPrefix() with backward compat
internal/tools/policy.go Add StripToolPrefix() function
ui/web/.../tool-policy-section.tsx Add prefix input field
ui/web/.../agent-config-tab.tsx Wire prefix state + save
ui/web/src/types/agent.ts Add toolCallPrefix to ToolPolicyConfig
ui/web/src/i18n/locales/{en,vi,zh}/agents.json i18n strings
.gitignore Ignore AI tool config directories

How It Works

LLM returns: proxy_exec({"command": "ls"})
                ↓
resolveToolCallName("proxy_exec")
                ↓
StripToolPrefix("proxy_{tool_name}", "proxy_exec") → "exec"
                ↓
registryName = "exec"  (used for policy check, registry lookup, spawn detection)
tc.Name = "proxy_exec" (preserved for logs and spans)

Test Plan

  • Configure agent with toolCallPrefix: "proxy_{tool_name}"
  • Send message that triggers tool calls
  • Verify tools execute correctly (prefix stripped before registry lookup)
  • Verify tool policy allow/deny works with canonical names
  • Verify team_tasks and spawn detection works with prefixed names
  • Verify parallel tool execution also strips prefix correctly
  • Verify agents without prefix configured still work (no regression)
  • UI: prefix input saves and persists across reload

Proxy providers like LiteLLM and OpenRouter may prepend a prefix to
tool call names returned by the model (e.g. "proxy_exec" instead of
"exec"). This broke tool policy validation, registry lookup, and
hardcoded name checks for "team_tasks" and "spawn" in both serial
and parallel execution paths.

Add per-agent toolCallPrefix configuration that strips the configured
prefix from incoming tool call names before registry resolution. The
stripping is applied at resolveToolCallName() which is called before
permission checks, registry execution, and spawn/team_tasks detection.

- Add StripToolPrefix() supporting literal ("proxy_") and template
  ("{tool_name}") patterns
- Add toolCallPrefix to ToolPolicySpec with backward compat from old
  "toolPrefix" JSON key
- Fix config save using spread operator to prevent dropping new fields
- Add UI input in Tool Policy section with i18n (en/vi/zh)
Add .gemini/, .claude/, .opencode/ to .gitignore to prevent
committing user-specific AI tool configurations.
…-stripping

# Conflicts:
#	internal/agent/loop.go
#	ui/web/src/pages/agents/agent-detail/agent-config-tab.tsx
@viettranx viettranx merged commit ffb5368 into nextlevelbuilder:main Mar 20, 2026
2 of 3 checks passed
viettranx added a commit that referenced this pull request Mar 20, 2026
- Fix TrimLeft → TrimPrefix to strip only one underscore separator
- Add registryName to indexedResult for parallel path cache
- Use registryName for bootstrapToolAllowlist and loopDetector checks
- Add server-side sanitization for ToolCallPrefix input
- Remove dead chatMessages alias and unrelated ParseStripAssistantPrefill
- Remove orphaned stripAssistantPrefill i18n keys
- Add 13 unit tests for StripToolPrefix
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants