remove call_tool and pipe implementations

This commit is contained in:
ollie 2026-07-28 22:51:47 +02:00
parent 18d00191f8
commit 6bd77276ee
6 changed files with 7 additions and 292 deletions

View File

@ -54,7 +54,7 @@ func BuildRuntime(cfg *config.Config, d tools.Dispatcher, cwd string, env []stri
serverOf[t.Name] = t.Server
}
// Only built-in executors (with InputSchema) become backend tools.
// Named tool scripts are invoked via call_tool and only appear in the preamble.
// Named tool scripts are promoted via the tool registry and appear in the preamble.
allTools = toolInfosToBackend(allToolInfos)
// Append named tool scripts for preamble listing only.

View File

@ -247,7 +247,7 @@ func run(ctx context.Context, cfg agentConfig, state state) error {
// Parse text-based tool calls: models that don't support the function
// calling API emit tool invocations as plain text (e.g.
// "call_tool: calls=[...]"). Parse these into proper ToolCall structs.
// "file_read: args=[...]" (text-based tool call syntax)). Parse these into proper ToolCall structs.
if len(toolCalls) == 0 && len(cfg.Tools) > 0 {
if parsed := parseTextToolCalls(content.String(), cfg.Tools); len(parsed) > 0 {
toolCalls = parsed
@ -717,7 +717,7 @@ func parseTextToolCalls(text string, tools []backend.Tool) []backend.ToolCall {
}
var calls []backend.ToolCall
for _, name := range []string{"execute_code", "call_tool", "pipe"} {
for _, name := range []string{"execute_code"} {
if !toolNames[name] {
continue
}

View File

@ -117,10 +117,7 @@ func (e *Server) Dispatch(ctx context.Context, name string, args json.RawMessage
switch name {
case "execute_code":
return dispatchExecuteCode(ctx, e, args)
case "call_tool":
return dispatchCallTool(ctx, e, args)
case "pipe":
return dispatchPipeCall(ctx, e, args)
default:
return "", fmt.Errorf("unknown execute tool: %s", name)
}
@ -206,50 +203,7 @@ func dispatchExecuteCode(ctx context.Context, e *Server, args json.RawMessage) (
return e.dispatchSteps(ctx, steps, timeout, sandboxName)
}
// dispatchCallTool handles the call_tool tool: named tool scripts only.
// Inline code fields are rejected. Fan-out behaviour is preserved.
func dispatchCallTool(ctx context.Context, e *Server, args json.RawMessage) (string, error) {
calls, timeout, sandboxName, err := execCallToolArgs(args)
if err != nil {
return "", fmt.Errorf("call_tool: bad args: %w. Expected format: {\"calls\": [{\"tool\": \"tool_name\", \"args\": [\"arg1\", ...]}]}", err)
}
if len(calls) == 0 {
return "", fmt.Errorf("call_tool: calls is required. Format: {\"calls\": [{\"tool\": \"tool_name\", \"args\": [\"arg1\", ...]}]}")
}
// Validate: every top-level call must name a tool (or be a parallel group of tools).
for i, c := range calls {
if len(c.Parallel) > 0 {
for j, p := range c.Parallel {
if p.Tool == "" {
return "", fmt.Errorf("call_tool: calls[%d].parallel[%d]: tool name is required", i, j)
}
if len(e.allowTools) > 0 && !e.allowTools[p.Tool] {
return "", fmt.Errorf("call_tool: tool %q not permitted in this agent context", p.Tool)
}
}
continue
}
if c.Tool == "" {
return "", fmt.Errorf("call_tool: calls[%d]: tool name is required", i)
}
if len(e.allowTools) > 0 && !e.allowTools[c.Tool] {
return "", fmt.Errorf("call_tool: tool %q not permitted in this agent context", c.Tool)
}
}
return e.dispatchSteps(ctx, calls, timeout, sandboxName)
}
// dispatchPipeCall handles the pipe tool: sequential pipeline of code and/or tool stages.
func dispatchPipeCall(ctx context.Context, e *Server, args json.RawMessage) (string, error) {
stages, timeout, sandboxName, err := execPipeArgs(args)
if err != nil {
return "", fmt.Errorf("pipe: bad args: %w", err)
}
if len(stages) == 0 {
return "", fmt.Errorf("pipe: stages is required. Format: {\"stages\": [{\"code\": \"...\"} or {\"tool\": \"name\", \"args\": [...]}]}")
}
return e.dispatchPipe(ctx, stages, timeout, sandboxName)
}
// dispatchSteps runs steps in parallel when safe (annotation-based), serially otherwise.
func (e *Server) dispatchSteps(ctx context.Context, steps []CodeStep, timeout int, sandboxName string) (string, error) {
@ -308,38 +262,7 @@ func (e *Server) dispatchSteps(ctx context.Context, steps []CodeStep, timeout in
return output, nil
}
// dispatchPipe runs stages as an explicit pipeline: sequential, stdout of each
// stage feeds stdin of the next.
func (e *Server) dispatchPipe(ctx context.Context, pipe []CodeStep, timeout int, sandboxName string) (string, error) {
if e.Strict {
if err := enforceStrict(pipe); err != nil {
return "", err
}
}
if len(e.allowTools) > 0 {
for i, s := range pipe {
if s.Tool != "" && !e.allowTools[s.Tool] {
return "", fmt.Errorf("pipe: stage %d: tool %q not permitted in this agent context", i, s.Tool)
}
for j, p := range s.Parallel {
if p.Tool != "" && !e.allowTools[p.Tool] {
return "", fmt.Errorf("pipe: stage %d.parallel[%d]: tool %q not permitted in this agent context", i, j, p.Tool)
}
}
}
}
var input string
for i, stage := range pipe {
out, err := e.runStage(ctx, i, stage, timeout, sandboxName, input)
if err != nil {
return out, fmt.Errorf("pipe stage %d: %w", i, err)
}
input = out
}
return input, nil
}
// enforceStrict rejects any step that uses inline code rather than a named tool.
func enforceStrict(stages []CodeStep) error {
@ -379,46 +302,4 @@ func execCodeOnlyArgs(args json.RawMessage) (steps []CodeStep, timeout int, sand
return
}
// execCallToolArgs parses args for the call_tool tool (calls array).
func execCallToolArgs(args json.RawMessage) (calls []CodeStep, timeout int, sandboxName string, err error) {
var a struct {
Calls []CodeStep `json:"calls"`
Timeout int `json:"timeout"`
Sandbox string `json:"sandbox"`
}
if err = json.Unmarshal(args, &a); err != nil {
return
}
calls = a.Calls
timeout = a.Timeout
if timeout <= 0 {
timeout = 30
}
sandboxName = a.Sandbox
if sandboxName == "" {
sandboxName = "default"
}
return
}
// execPipeArgs parses args for the pipe tool (stages array).
func execPipeArgs(args json.RawMessage) (stages []CodeStep, timeout int, sandboxName string, err error) {
var a struct {
Stages []CodeStep `json:"stages"`
Timeout int `json:"timeout"`
Sandbox string `json:"sandbox"`
}
if err = json.Unmarshal(args, &a); err != nil {
return
}
stages = a.Stages
timeout = a.Timeout
if timeout <= 0 {
timeout = 30
}
sandboxName = a.Sandbox
if sandboxName == "" {
sandboxName = "default"
}
return
}

View File

@ -189,116 +189,7 @@ Examples:
}
}`),
},
{
Name: "call_tool",
Description: `Run one or more named scripts from the tools directory.
Tool names and their arguments are discoverable via:
grep -iA2 'keyword' $OLLIE_TOOLS_PATH/idx
call_tool fans out in parallel when consecutive calls carry an ollie:parallel read
annotation in their script header; write-annotated or unannotated tools run serially.
Outputs are concatenated in submission order.
No stdout chaining between calls — use pipe for that.
Each call is one of:
- {tool, args} — named script with arguments
- {tool, args, elevated: true} — run outside sandbox (bash tools only)
- {parallel: [{tool, args}...]} — explicit concurrent fan-out
Examples:
- Single call: calls=[{tool: "file_read", args: ["README.md"]}]
- Parallel reads: calls=[{tool: "file_read", args: ["a.txt"]}, {tool: "file_read", args: ["b.txt"]}]`,
InputSchema: json.RawMessage(`{
"type": "object",
"required": ["calls"],
"properties": {
"calls": {
"type": "array",
"description": "Tool calls. Fanned out in parallel when safe (ollie:parallel annotation), serially otherwise.",
"items": {
"type": "object",
"properties": {
"tool": {"type": "string", "description": "Named tool script from the tools directory."},
"args": {"type": "array", "items": {"type": "string"}, "description": "Arguments for the tool script."},
"elevated": {"type": "boolean", "description": "Run outside the sandbox via the elevation backend."},
"parallel": {
"type": "array",
"description": "Explicit fan-out: calls run concurrently, outputs concatenated in submission order.",
"items": {
"type": "object",
"properties": {
"tool": {"type": "string"},
"args": {"type": "array", "items": {"type": "string"}},
"elevated": {"type": "boolean"}
}
}
}
}
}
},
"timeout": {"type": "integer", "description": "Timeout in seconds per call (default: 30). Use 0 for no timeout."},
"sandbox": {"type": "string", "description": "Sandbox name (default: default)."}
}
}`),
},
{
Name: "pipe",
Description: `Compose execute_code and call_tool steps into a sequential pipeline.
Stages run in order; each stage's stdout becomes the next stage's stdin.
Use this when you need to chain heterogeneous code and tool steps together.
Each stage is one of:
- {code, language} — inline code (default: bash)
- {tool, args} — named script from the tools directory
- {elevated: true, code/tool} — run outside sandbox
- {parallel: [{code/tool}...]} — fan-out within a stage; outputs concatenated, fed to next stage
Supported inline languages: bash, python3, perl, lua, awk, sed, jq, ed, expect, bc.
timeout applies to each stage independently (default: 30s). A failed stage aborts.
Examples:
- Code → code: stages=[{code: "grep error app.log"}, {code: "wc -l"}]
- Tool → code: stages=[{tool: "fetch.sh", args: ["--last=1h"]}, {code: "jq .result"}]
- Fan-out → code: stages=[{parallel: [{code: "cat a.txt"}, {code: "cat b.txt"}]}, {code: "sort"}]`,
InputSchema: json.RawMessage(`{
"type": "object",
"required": ["stages"],
"properties": {
"stages": {
"type": "array",
"description": "Pipeline stages. Each stage's stdout feeds the next stage's stdin.",
"items": {
"type": "object",
"properties": {
"code": {"type": "string", "description": "Inline code to execute."},
"language": {"type": "string", "description": "Language interpreter (default: bash). Ignored when tool or parallel is set."},
"tool": {"type": "string", "description": "Named tool script from the tools directory."},
"args": {"type": "array", "items": {"type": "string"}, "description": "Arguments for the tool script."},
"elevated": {"type": "boolean", "description": "Run outside the sandbox via the elevation backend."},
"parallel": {
"type": "array",
"description": "Fan-out within a stage: steps run concurrently, outputs concatenated, then fed to next stage.",
"items": {
"type": "object",
"properties": {
"code": {"type": "string"},
"language": {"type": "string"},
"tool": {"type": "string"},
"args": {"type": "array", "items": {"type": "string"}},
"elevated": {"type": "boolean"}
}
}
}
}
}
},
"timeout": {"type": "integer", "description": "Timeout in seconds per stage (default: 30). Use 0 for no timeout."},
"sandbox": {"type": "string", "description": "Sandbox name (default: default)."}
}
}`),
},
}
if e.toolRegistry != nil && e.sessionID != "" {

View File

@ -37,14 +37,10 @@ func (e *Server) ResultTier(name string) string {
}
// ResultTierArgs classifies the tier using both the outer tool name and its
// arguments. For call_tool and pipe, it inspects inner tool names and returns
// the coldest tier found. Tier ordering: cold < warm < hot.
// arguments. For execute_code, it returns warm. For promoted tools, it
// delegates to ResultTier.
func (e *Server) ResultTierArgs(name string, args json.RawMessage) string {
switch name {
case "call_tool":
return e.tierFromCallToolArgs(args)
case "pipe":
return e.tierFromPipeArgs(args)
case "execute_code":
return "warm"
default:
@ -52,58 +48,6 @@ func (e *Server) ResultTierArgs(name string, args json.RawMessage) string {
}
}
func (e *Server) tierFromCallToolArgs(args json.RawMessage) string {
var parsed struct {
Calls []struct {
Tool string `json:"tool"`
Parallel []struct {
Tool string `json:"tool"`
} `json:"parallel"`
} `json:"calls"`
}
if json.Unmarshal(args, &parsed) != nil || len(parsed.Calls) == 0 {
return "hot"
}
coldest := "hot"
for _, c := range parsed.Calls {
if c.Tool != "" {
coldest = colderTier(coldest, e.ResultTier(c.Tool))
}
for _, p := range c.Parallel {
if p.Tool != "" {
coldest = colderTier(coldest, e.ResultTier(p.Tool))
}
}
}
return coldest
}
func (e *Server) tierFromPipeArgs(args json.RawMessage) string {
var parsed struct {
Stages []struct {
Tool string `json:"tool"`
Parallel []struct {
Tool string `json:"tool"`
} `json:"parallel"`
} `json:"stages"`
}
if json.Unmarshal(args, &parsed) != nil || len(parsed.Stages) == 0 {
return "hot"
}
coldest := "hot"
for _, s := range parsed.Stages {
if s.Tool != "" {
coldest = colderTier(coldest, e.ResultTier(s.Tool))
}
for _, p := range s.Parallel {
if p.Tool != "" {
coldest = colderTier(coldest, e.ResultTier(p.Tool))
}
}
}
return coldest
}
// colderTier returns the colder of two tiers. cold < warm < hot.
func colderTier(a, b string) string {
order := map[string]int{"cold": 0, "warm": 1, "hot": 2}

View File

@ -129,7 +129,6 @@ type ParallelClassifier interface {
type TierClassifier interface {
ResultTier(name string) string
// ResultTierArgs classifies the tier using both the outer tool name and its
// arguments. For multiplexed tools (call_tool, pipe), this inspects the
// inner tool names. Returns the coldest tier among all inner tools.
// arguments.
ResultTierArgs(name string, args json.RawMessage) string
}