remove call_tool and pipe implementations
This commit is contained in:
parent
18d00191f8
commit
6bd77276ee
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 != "" {
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Reference in New Issue