1// Package main provides a comprehensive streaming agent example of using the fantasy AI SDK.
  2package main
  3
  4import (
  5	"context"
  6	"fmt"
  7	"os"
  8	"strings"
  9
 10	"charm.land/fantasy"
 11	"charm.land/fantasy/providers/anthropic"
 12)
 13
 14func main() {
 15	// Check for API key
 16	apiKey := os.Getenv("ANTHROPIC_API_KEY")
 17	if apiKey == "" {
 18		fmt.Println("❌ Please set ANTHROPIC_API_KEY environment variable")
 19		fmt.Println("   export ANTHROPIC_API_KEY=your_api_key_here")
 20		os.Exit(1)
 21	}
 22
 23	fmt.Println("🚀 Streaming Agent Example")
 24	fmt.Println("==========================")
 25	fmt.Println()
 26
 27	// Create OpenAI provider and model
 28	provider := anthropic.New(anthropic.WithAPIKey(os.Getenv("ANTHROPIC_API_KEY")))
 29	model, err := provider.LanguageModel("claude-sonnet-4-20250514")
 30	if err != nil {
 31		fmt.Println(err)
 32		return
 33	}
 34
 35	// Define input types for type-safe tools
 36	type WeatherInput struct {
 37		Location string `json:"location" description:"The city and country, e.g. 'London, UK'"`
 38		Unit     string `json:"unit,omitempty" enum:"celsius,fahrenheit" description:"Temperature unit (celsius or fahrenheit)"`
 39	}
 40
 41	type CalculatorInput struct {
 42		Expression string `json:"expression" description:"Mathematical expression to evaluate (e.g., '2 + 2', '10 * 5')"`
 43	}
 44
 45	// Create weather tool using the new type-safe API
 46	weatherTool := fantasy.NewAgentTool(
 47		"get_weather",
 48		"Get the current weather for a specific location",
 49		func(_ context.Context, input WeatherInput, _ fantasy.ToolCall) (fantasy.ToolResponse, error) {
 50			// Simulate weather lookup with some fake data
 51			location := input.Location
 52			if location == "" {
 53				location = "Unknown"
 54			}
 55
 56			// Default to celsius if not specified
 57			unit := input.Unit
 58			if unit == "" {
 59				unit = "celsius"
 60			}
 61
 62			// Simulate different temperatures for different cities
 63			var temp string
 64			if strings.Contains(strings.ToLower(location), "pristina") {
 65				temp = "15°C"
 66				if unit == "fahrenheit" {
 67					temp = "59°F"
 68				}
 69			} else if strings.Contains(strings.ToLower(location), "london") {
 70				temp = "12°C"
 71				if unit == "fahrenheit" {
 72					temp = "54°F"
 73				}
 74			} else {
 75				temp = "22°C"
 76				if unit == "fahrenheit" {
 77					temp = "72°F"
 78				}
 79			}
 80
 81			weather := fmt.Sprintf("The current weather in %s is %s with partly cloudy skies and light winds.", location, temp)
 82			return fantasy.NewTextResponse(weather), nil
 83		},
 84	)
 85
 86	// Create calculator tool using the new type-safe API
 87	calculatorTool := fantasy.NewAgentTool(
 88		"calculate",
 89		"Perform basic mathematical calculations",
 90		func(_ context.Context, input CalculatorInput, _ fantasy.ToolCall) (fantasy.ToolResponse, error) {
 91			// Simple calculator simulation
 92			expr := strings.TrimSpace(input.Expression)
 93			if strings.Contains(expr, "2 + 2") || strings.Contains(expr, "2+2") {
 94				return fantasy.NewTextResponse("2 + 2 = 4"), nil
 95			} else if strings.Contains(expr, "10 * 5") || strings.Contains(expr, "10*5") {
 96				return fantasy.NewTextResponse("10 * 5 = 50"), nil
 97			} else if strings.Contains(expr, "15 + 27") || strings.Contains(expr, "15+27") {
 98				return fantasy.NewTextResponse("15 + 27 = 42"), nil
 99			}
100			return fantasy.NewTextResponse("I can calculate simple expressions like '2 + 2', '10 * 5', or '15 + 27'"), nil
101		},
102	)
103
104	// Create agent with tools
105	agent := fantasy.NewAgent(
106		model,
107		fantasy.WithSystemPrompt("You are a helpful assistant that can check weather and do calculations. Be concise and friendly."),
108		fantasy.WithTools(weatherTool, calculatorTool),
109	)
110
111	ctx := context.Background()
112
113	// Demonstrate streaming with comprehensive callbacks
114	fmt.Println("💬 Asking: \"What's the weather in Pristina and what's 2 + 2?\"")
115	fmt.Println()
116
117	// Track streaming events
118	var stepCount int
119	var textBuffer strings.Builder
120	var reasoningBuffer strings.Builder
121
122	// Create streaming call with all callbacks
123	streamCall := fantasy.AgentStreamCall{
124		Prompt: "What's the weather in Pristina and what's 2 + 2?",
125
126		// Agent-level callbacks
127		OnAgentStart: func() {
128			fmt.Println("🎬 Agent started")
129		},
130		OnAgentFinish: func(result *fantasy.AgentResult) error {
131			fmt.Printf("🏁 Agent finished with %d steps, total tokens: %d\n", len(result.Steps), result.TotalUsage.TotalTokens)
132			return nil
133		},
134		OnStepStart: func(stepNumber int) error {
135			stepCount++
136			fmt.Printf("📝 Step %d started\n", stepNumber+1)
137			return nil
138		},
139		OnStepFinish: func(stepResult fantasy.StepResult) error {
140			fmt.Printf("✅ Step completed (reason: %s, tokens: %d)\n", stepResult.FinishReason, stepResult.Usage.TotalTokens)
141			return nil
142		},
143		OnFinish: func(result *fantasy.AgentResult) {
144			fmt.Printf("🎯 Final result ready with %d steps\n", len(result.Steps))
145		},
146		OnError: func(err error) {
147			fmt.Printf("❌ Error: %v\n", err)
148		},
149
150		// Stream part callbacks
151		OnWarnings: func(warnings []fantasy.CallWarning) error {
152			for _, warning := range warnings {
153				fmt.Printf("⚠️  Warning: %s\n", warning.Message)
154			}
155			return nil
156		},
157		OnTextStart: func(_ string) error {
158			fmt.Print("💭 Assistant: ")
159			return nil
160		},
161		OnTextDelta: func(_ string, text string) error {
162			fmt.Print(text)
163			textBuffer.WriteString(text)
164			return nil
165		},
166		OnTextEnd: func(_ string) error {
167			fmt.Println()
168			return nil
169		},
170		OnReasoningStart: func(_ string, _ fantasy.ReasoningContent) error {
171			fmt.Print("🤔 Thinking: ")
172			return nil
173		},
174		OnReasoningDelta: func(_ string, text string) error {
175			reasoningBuffer.WriteString(text)
176			return nil
177		},
178		OnReasoningEnd: func(_ string, _ fantasy.ReasoningContent) error {
179			if reasoningBuffer.Len() > 0 {
180				fmt.Printf("%s\n", reasoningBuffer.String())
181				reasoningBuffer.Reset()
182			}
183			return nil
184		},
185		OnToolInputStart: func(_ string, toolName string) error {
186			fmt.Printf("🔧 Calling tool: %s\n", toolName)
187			return nil
188		},
189		OnToolInputDelta: func(_ string, _ string) error {
190			// Could show tool input being built, but it's often noisy
191			return nil
192		},
193		OnToolInputEnd: func(_ string) error {
194			// Tool input complete
195			return nil
196		},
197		OnToolCall: func(toolCall fantasy.ToolCallContent) error {
198			fmt.Printf("🛠️  Tool call: %s\n", toolCall.ToolName)
199			fmt.Printf("   Input: %s\n", toolCall.Input)
200			return nil
201		},
202		OnToolResult: func(result fantasy.ToolResultContent) error {
203			fmt.Printf("🎯 Tool result from %s:\n", result.ToolName)
204			switch output := result.Result.(type) {
205			case fantasy.ToolResultOutputContentText:
206				fmt.Printf("   %s\n", output.Text)
207			case fantasy.ToolResultOutputContentError:
208				fmt.Printf("   Error: %s\n", output.Error.Error())
209			}
210			return nil
211		},
212		OnSource: func(source fantasy.SourceContent) error {
213			fmt.Printf("📚 Source: %s (%s)\n", source.Title, source.URL)
214			return nil
215		},
216		OnStreamFinish: func(usage fantasy.Usage, finishReason fantasy.FinishReason, _ fantasy.ProviderMetadata) error {
217			fmt.Printf("📊 Stream finished (reason: %s, tokens: %d)\n", finishReason, usage.TotalTokens)
218			return nil
219		},
220	}
221
222	// Execute streaming agent
223	result, err := agent.Stream(ctx, streamCall)
224	if err != nil {
225		fmt.Printf("❌ Agent failed: %v\n", err)
226		os.Exit(1)
227	}
228
229	// Display final results
230	fmt.Println()
231	fmt.Println("📋 Final Summary")
232	fmt.Println("================")
233	fmt.Printf("Steps executed: %d\n", len(result.Steps))
234	fmt.Printf("Total tokens used: %d (input: %d, output: %d)\n",
235		result.TotalUsage.TotalTokens,
236		result.TotalUsage.InputTokens,
237		result.TotalUsage.OutputTokens)
238
239	if result.TotalUsage.ReasoningTokens > 0 {
240		fmt.Printf("Reasoning tokens: %d\n", result.TotalUsage.ReasoningTokens)
241	}
242
243	fmt.Printf("Final response: %s\n", result.Response.Content.Text())
244
245	// Show step details
246	fmt.Println()
247	fmt.Println("🔍 Step Details")
248	fmt.Println("===============")
249	for i, step := range result.Steps {
250		fmt.Printf("Step %d:\n", i+1)
251		fmt.Printf("  Finish reason: %s\n", step.FinishReason)
252		fmt.Printf("  Content types: ")
253
254		var contentTypes []string
255		for _, content := range step.Content {
256			contentTypes = append(contentTypes, string(content.GetType()))
257		}
258		fmt.Printf("%s\n", strings.Join(contentTypes, ", "))
259
260		// Show tool calls and results
261		toolCalls := step.Content.ToolCalls()
262		if len(toolCalls) > 0 {
263			fmt.Printf("  Tool calls: ")
264			var toolNames []string
265			for _, tc := range toolCalls {
266				toolNames = append(toolNames, tc.ToolName)
267			}
268			fmt.Printf("%s\n", strings.Join(toolNames, ", "))
269		}
270
271		toolResults := step.Content.ToolResults()
272		if len(toolResults) > 0 {
273			fmt.Printf("  Tool results: %d\n", len(toolResults))
274		}
275
276		fmt.Printf("  Tokens: %d\n", step.Usage.TotalTokens)
277		fmt.Println()
278	}
279
280	fmt.Println("✨ Example completed successfully!")
281}