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
|
serverOf[t.Name] = t.Server
|
||||||
}
|
}
|
||||||
// Only built-in executors (with InputSchema) become backend tools.
|
// 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)
|
allTools = toolInfosToBackend(allToolInfos)
|
||||||
|
|
||||||
// Append named tool scripts for preamble listing only.
|
// 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
|
// Parse text-based tool calls: models that don't support the function
|
||||||
// calling API emit tool invocations as plain text (e.g.
|
// 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 len(toolCalls) == 0 && len(cfg.Tools) > 0 {
|
||||||
if parsed := parseTextToolCalls(content.String(), cfg.Tools); len(parsed) > 0 {
|
if parsed := parseTextToolCalls(content.String(), cfg.Tools); len(parsed) > 0 {
|
||||||
toolCalls = parsed
|
toolCalls = parsed
|
||||||
|
|
@ -717,7 +717,7 @@ func parseTextToolCalls(text string, tools []backend.Tool) []backend.ToolCall {
|
||||||
}
|
}
|
||||||
|
|
||||||
var calls []backend.ToolCall
|
var calls []backend.ToolCall
|
||||||
for _, name := range []string{"execute_code", "call_tool", "pipe"} {
|
for _, name := range []string{"execute_code"} {
|
||||||
if !toolNames[name] {
|
if !toolNames[name] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,10 +117,7 @@ func (e *Server) Dispatch(ctx context.Context, name string, args json.RawMessage
|
||||||
switch name {
|
switch name {
|
||||||
case "execute_code":
|
case "execute_code":
|
||||||
return dispatchExecuteCode(ctx, e, args)
|
return dispatchExecuteCode(ctx, e, args)
|
||||||
case "call_tool":
|
|
||||||
return dispatchCallTool(ctx, e, args)
|
|
||||||
case "pipe":
|
|
||||||
return dispatchPipeCall(ctx, e, args)
|
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("unknown execute tool: %s", name)
|
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)
|
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.
|
// 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) {
|
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
|
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.
|
// enforceStrict rejects any step that uses inline code rather than a named tool.
|
||||||
func enforceStrict(stages []CodeStep) error {
|
func enforceStrict(stages []CodeStep) error {
|
||||||
|
|
@ -379,46 +302,4 @@ func execCodeOnlyArgs(args json.RawMessage) (steps []CodeStep, timeout int, sand
|
||||||
return
|
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 != "" {
|
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
|
// 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
|
// arguments. For execute_code, it returns warm. For promoted tools, it
|
||||||
// the coldest tier found. Tier ordering: cold < warm < hot.
|
// delegates to ResultTier.
|
||||||
func (e *Server) ResultTierArgs(name string, args json.RawMessage) string {
|
func (e *Server) ResultTierArgs(name string, args json.RawMessage) string {
|
||||||
switch name {
|
switch name {
|
||||||
case "call_tool":
|
|
||||||
return e.tierFromCallToolArgs(args)
|
|
||||||
case "pipe":
|
|
||||||
return e.tierFromPipeArgs(args)
|
|
||||||
case "execute_code":
|
case "execute_code":
|
||||||
return "warm"
|
return "warm"
|
||||||
default:
|
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.
|
// colderTier returns the colder of two tiers. cold < warm < hot.
|
||||||
func colderTier(a, b string) string {
|
func colderTier(a, b string) string {
|
||||||
order := map[string]int{"cold": 0, "warm": 1, "hot": 2}
|
order := map[string]int{"cold": 0, "warm": 1, "hot": 2}
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,6 @@ type ParallelClassifier interface {
|
||||||
type TierClassifier interface {
|
type TierClassifier interface {
|
||||||
ResultTier(name string) string
|
ResultTier(name string) string
|
||||||
// ResultTierArgs classifies the tier using both the outer tool name and its
|
// ResultTierArgs classifies the tier using both the outer tool name and its
|
||||||
// arguments. For multiplexed tools (call_tool, pipe), this inspects the
|
// arguments.
|
||||||
// inner tool names. Returns the coldest tier among all inner tools.
|
|
||||||
ResultTierArgs(name string, args json.RawMessage) string
|
ResultTierArgs(name string, args json.RawMessage) string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Reference in New Issue