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
18 Description string
19 Parameters map[string]any
20 Required []string
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}