tool.go

  1package fantasy
  2
  3import (
  4	"context"
  5	"encoding/json"
  6	"fmt"
  7	"reflect"
  8
  9	"charm.land/fantasy/schema"
 10)
 11
 12// Schema represents a JSON schema for tool input validation.
 13type Schema = schema.Schema
 14
 15// ToolInfo represents tool metadata, matching the existing pattern.
 16type ToolInfo struct {
 17	Name        string         `json:"name"`
 18	Description string         `json:"description"`
 19	Parameters  map[string]any `json:"parameters"`
 20	Required    []string       `json:"required"`
 21}
 22
 23// ToolCall represents a tool invocation, matching the existing pattern.
 24type ToolCall struct {
 25	ID    string `json:"id"`
 26	Name  string `json:"name"`
 27	Input string `json:"input"`
 28}
 29
 30// ToolResponse represents the response from a tool execution, matching the existing pattern.
 31type ToolResponse struct {
 32	Type     string `json:"type"`
 33	Content  string `json:"content"`
 34	Metadata string `json:"metadata,omitempty"`
 35	IsError  bool   `json:"is_error"`
 36}
 37
 38// NewTextResponse creates a text response.
 39func NewTextResponse(content string) ToolResponse {
 40	return ToolResponse{
 41		Type:    "text",
 42		Content: content,
 43	}
 44}
 45
 46// NewTextErrorResponse creates an error response.
 47func NewTextErrorResponse(content string) ToolResponse {
 48	return ToolResponse{
 49		Type:    "text",
 50		Content: content,
 51		IsError: true,
 52	}
 53}
 54
 55// WithResponseMetadata adds metadata to a response.
 56func WithResponseMetadata(response ToolResponse, metadata any) ToolResponse {
 57	if metadata != nil {
 58		metadataBytes, err := json.Marshal(metadata)
 59		if err != nil {
 60			return response
 61		}
 62		response.Metadata = string(metadataBytes)
 63	}
 64	return response
 65}
 66
 67// AgentTool represents a tool that can be called by a language model.
 68// This matches the existing BaseTool interface pattern.
 69type AgentTool interface {
 70	Info() ToolInfo
 71	Run(ctx context.Context, params ToolCall) (ToolResponse, error)
 72	ProviderOptions() ProviderOptions
 73	SetProviderOptions(opts ProviderOptions)
 74}
 75
 76// NewAgentTool creates a typed tool from a function with automatic schema generation.
 77// This is the recommended way to create tools.
 78func NewAgentTool[TInput any](
 79	name string,
 80	description string,
 81	fn func(ctx context.Context, input TInput, call ToolCall) (ToolResponse, error),
 82) AgentTool {
 83	var input TInput
 84	schema := schema.Generate(reflect.TypeOf(input))
 85
 86	return &funcToolWrapper[TInput]{
 87		name:        name,
 88		description: description,
 89		fn:          fn,
 90		schema:      schema,
 91	}
 92}
 93
 94// funcToolWrapper wraps a function to implement the AgentTool interface.
 95type funcToolWrapper[TInput any] struct {
 96	name            string
 97	description     string
 98	fn              func(ctx context.Context, input TInput, call ToolCall) (ToolResponse, error)
 99	schema          Schema
100	providerOptions ProviderOptions
101}
102
103func (w *funcToolWrapper[TInput]) SetProviderOptions(opts ProviderOptions) {
104	w.providerOptions = opts
105}
106
107func (w *funcToolWrapper[TInput]) ProviderOptions() ProviderOptions {
108	return w.providerOptions
109}
110
111func (w *funcToolWrapper[TInput]) Info() ToolInfo {
112	if w.schema.Required == nil {
113		w.schema.Required = []string{}
114	}
115	return ToolInfo{
116		Name:        w.name,
117		Description: w.description,
118		Parameters:  schema.ToParameters(w.schema),
119		Required:    w.schema.Required,
120	}
121}
122
123func (w *funcToolWrapper[TInput]) Run(ctx context.Context, params ToolCall) (ToolResponse, error) {
124	var input TInput
125	if err := json.Unmarshal([]byte(params.Input), &input); err != nil {
126		return NewTextErrorResponse(fmt.Sprintf("invalid parameters: %s", err)), nil
127	}
128
129	return w.fn(ctx, input, params)
130}