package tool import ( "context" "encoding/json" "errors" llm "gitea.stevedudenhoeffer.com/steve/majordomo/llm" ) // toolCall mirrors the legacy gollm-era test shape (string arguments) so the // pre-conversion test call sites keep their literal syntax. majordomo's // llm.ToolCall carries json.RawMessage arguments; execBox adapts. type toolCall struct { Name string Arguments string } // execBox executes one call through a toolbox and adapts majordomo's // ToolResult to the (result, error) pair these tests assert against: // IsError results come back as a Go error carrying the result content // (which is how the agent-facing error text read in the legacy gollm era). func execBox(box *llm.Toolbox, call toolCall) (string, error) { res := box.Execute(context.Background(), llm.ToolCall{ ID: "test-call", Name: call.Name, Arguments: json.RawMessage(call.Arguments), }) if res.IsError { return "", errors.New(res.Content) } return res.Content, nil } // execTool runs a single built llm.Tool's handler and serializes the // result the way llm.ExecuteTool does. Replaces the legacy gollm // Tool.Execute(ctx, argsJSON) method the original tests called. // // Why the handler directly (vs llm.ExecuteTool): ExecuteTool flattens // handler errors into IsError result text, but several tests assert // error IDENTITY (errors.Is against sentinel errors the handlers // wrap). Calling the handler preserves the error value, matching the // legacy gollm Execute contract these tests were written against. func execTool(ctx context.Context, t llm.Tool, args string) (string, error) { raw := json.RawMessage(args) if len(raw) == 0 { raw = json.RawMessage("{}") } out, err := t.Handler(ctx, raw) if err != nil { return "", err } switch v := out.(type) { case nil: return "null", nil case string: return v, nil case json.RawMessage: return string(v), nil default: enc, mErr := json.Marshal(v) if mErr != nil { return "", mErr } return string(enc), nil } }