agent.go

   1package fantasy
   2
   3import (
   4	"cmp"
   5	"context"
   6	"encoding/json"
   7	"errors"
   8	"fmt"
   9	"maps"
  10	"slices"
  11	"time"
  12)
  13
  14// StepResult represents the result of a single step in an agent execution.
  15type StepResult struct {
  16	Response
  17	Messages []Message
  18}
  19
  20// stepExecutionResult encapsulates the result of executing a step with stream processing.
  21type stepExecutionResult struct {
  22	StepResult     StepResult
  23	ShouldContinue bool
  24}
  25
  26// StopCondition defines a function that determines when an agent should stop executing.
  27type StopCondition = func(steps []StepResult) bool
  28
  29// StepCountIs returns a stop condition that stops after the specified number of steps.
  30func StepCountIs(stepCount int) StopCondition {
  31	return func(steps []StepResult) bool {
  32		return len(steps) >= stepCount
  33	}
  34}
  35
  36// HasToolCall returns a stop condition that stops when the specified tool is called in the last step.
  37func HasToolCall(toolName string) StopCondition {
  38	return func(steps []StepResult) bool {
  39		if len(steps) == 0 {
  40			return false
  41		}
  42		lastStep := steps[len(steps)-1]
  43		toolCalls := lastStep.Content.ToolCalls()
  44		for _, toolCall := range toolCalls {
  45			if toolCall.ToolName == toolName {
  46				return true
  47			}
  48		}
  49		return false
  50	}
  51}
  52
  53// HasContent returns a stop condition that stops when the specified content type appears in the last step.
  54func HasContent(contentType ContentType) StopCondition {
  55	return func(steps []StepResult) bool {
  56		if len(steps) == 0 {
  57			return false
  58		}
  59		lastStep := steps[len(steps)-1]
  60		for _, content := range lastStep.Content {
  61			if content.GetType() == contentType {
  62				return true
  63			}
  64		}
  65		return false
  66	}
  67}
  68
  69// FinishReasonIs returns a stop condition that stops when the specified finish reason occurs.
  70func FinishReasonIs(reason FinishReason) StopCondition {
  71	return func(steps []StepResult) bool {
  72		if len(steps) == 0 {
  73			return false
  74		}
  75		lastStep := steps[len(steps)-1]
  76		return lastStep.FinishReason == reason
  77	}
  78}
  79
  80// MaxTokensUsed returns a stop condition that stops when total token usage exceeds the specified limit.
  81func MaxTokensUsed(maxTokens int64) StopCondition {
  82	return func(steps []StepResult) bool {
  83		var totalTokens int64
  84		for _, step := range steps {
  85			totalTokens += step.Usage.TotalTokens
  86		}
  87		return totalTokens >= maxTokens
  88	}
  89}
  90
  91// PrepareStepFunctionOptions contains the options for preparing a step in an agent execution.
  92type PrepareStepFunctionOptions struct {
  93	Steps      []StepResult
  94	StepNumber int
  95	Model      LanguageModel
  96	Messages   []Message
  97}
  98
  99// PrepareStepResult contains the result of preparing a step in an agent execution.
 100type PrepareStepResult struct {
 101	Model           LanguageModel
 102	Messages        []Message
 103	System          *string
 104	ToolChoice      *ToolChoice
 105	ActiveTools     []string
 106	DisableAllTools bool
 107}
 108
 109// ToolCallRepairOptions contains the options for repairing a tool call.
 110type ToolCallRepairOptions struct {
 111	OriginalToolCall ToolCallContent
 112	ValidationError  error
 113	AvailableTools   []AgentTool
 114	SystemPrompt     string
 115	Messages         []Message
 116}
 117
 118type (
 119	// PrepareStepFunction defines a function that prepares a step in an agent execution.
 120	PrepareStepFunction = func(ctx context.Context, options PrepareStepFunctionOptions) (context.Context, PrepareStepResult, error)
 121
 122	// OnStepFinishedFunction defines a function that is called when a step finishes.
 123	OnStepFinishedFunction = func(step StepResult)
 124
 125	// RepairToolCallFunction defines a function that repairs a tool call.
 126	RepairToolCallFunction = func(ctx context.Context, options ToolCallRepairOptions) (*ToolCallContent, error)
 127)
 128
 129type agentSettings struct {
 130	systemPrompt     string
 131	maxOutputTokens  *int64
 132	temperature      *float64
 133	topP             *float64
 134	topK             *int64
 135	presencePenalty  *float64
 136	frequencyPenalty *float64
 137	headers          map[string]string
 138	providerOptions  ProviderOptions
 139
 140	// TODO: add support for provider tools
 141	tools      []AgentTool
 142	maxRetries *int
 143
 144	model LanguageModel
 145
 146	stopWhen       []StopCondition
 147	prepareStep    PrepareStepFunction
 148	repairToolCall RepairToolCallFunction
 149	onRetry        OnRetryCallback
 150}
 151
 152// AgentCall represents a call to an agent.
 153type AgentCall struct {
 154	Prompt           string     `json:"prompt"`
 155	Files            []FilePart `json:"files"`
 156	Messages         []Message  `json:"messages"`
 157	MaxOutputTokens  *int64
 158	Temperature      *float64 `json:"temperature"`
 159	TopP             *float64 `json:"top_p"`
 160	TopK             *int64   `json:"top_k"`
 161	PresencePenalty  *float64 `json:"presence_penalty"`
 162	FrequencyPenalty *float64 `json:"frequency_penalty"`
 163	ActiveTools      []string `json:"active_tools"`
 164	ProviderOptions  ProviderOptions
 165	OnRetry          OnRetryCallback
 166	MaxRetries       *int
 167
 168	StopWhen       []StopCondition
 169	PrepareStep    PrepareStepFunction
 170	RepairToolCall RepairToolCallFunction
 171}
 172
 173// Agent-level callbacks.
 174type (
 175	// OnAgentStartFunc is called when agent starts.
 176	OnAgentStartFunc func()
 177
 178	// OnAgentFinishFunc is called when agent finishes.
 179	OnAgentFinishFunc func(result *AgentResult) error
 180
 181	// OnStepStartFunc is called when a step starts.
 182	OnStepStartFunc func(stepNumber int) error
 183
 184	// OnStepFinishFunc is called when a step finishes.
 185	OnStepFinishFunc func(stepResult StepResult) error
 186
 187	// OnFinishFunc is called when entire agent completes.
 188	OnFinishFunc func(result *AgentResult)
 189
 190	// OnErrorFunc is called when an error occurs.
 191	OnErrorFunc func(error)
 192)
 193
 194// Stream part callbacks - called for each corresponding stream part type.
 195type (
 196	// OnChunkFunc is called for each stream part (catch-all).
 197	OnChunkFunc func(StreamPart) error
 198
 199	// OnWarningsFunc is called for warnings.
 200	OnWarningsFunc func(warnings []CallWarning) error
 201
 202	// OnTextStartFunc is called when text starts.
 203	OnTextStartFunc func(id string) error
 204
 205	// OnTextDeltaFunc is called for text deltas.
 206	OnTextDeltaFunc func(id, text string) error
 207
 208	// OnTextEndFunc is called when text ends.
 209	OnTextEndFunc func(id string) error
 210
 211	// OnReasoningStartFunc is called when reasoning starts.
 212	OnReasoningStartFunc func(id string, reasoning ReasoningContent) error
 213
 214	// OnReasoningDeltaFunc is called for reasoning deltas.
 215	OnReasoningDeltaFunc func(id, text string) error
 216
 217	// OnReasoningEndFunc is called when reasoning ends.
 218	OnReasoningEndFunc func(id string, reasoning ReasoningContent) error
 219
 220	// OnToolInputStartFunc is called when tool input starts.
 221	OnToolInputStartFunc func(id, toolName string) error
 222
 223	// OnToolInputDeltaFunc is called for tool input deltas.
 224	OnToolInputDeltaFunc func(id, delta string) error
 225
 226	// OnToolInputEndFunc is called when tool input ends.
 227	OnToolInputEndFunc func(id string) error
 228
 229	// OnToolCallFunc is called when tool call is complete.
 230	OnToolCallFunc func(toolCall ToolCallContent) error
 231
 232	// PreToolExecuteFunc is called before tool execution.
 233	// Can modify the tool call or return an error to skip execution.
 234	// Returning a modified ToolCall allows changing input parameters.
 235	// Returning an error creates an error result without executing the tool.
 236	PreToolExecuteFunc func(ctx context.Context, toolCall ToolCall) (context.Context, *ToolCall, error)
 237
 238	// OnToolResultFunc is called when tool execution completes.
 239	OnToolResultFunc func(result ToolResultContent) error
 240
 241	// PostToolExecuteFunc is called after tool execution, before sending result to LLM.
 242	// Can modify the tool response or return an error to replace the response.
 243	// Returning a modified ToolResponse allows filtering or redacting output.
 244	PostToolExecuteFunc func(ctx context.Context, toolCall ToolCall, response ToolResponse, executionTimeMs int64) (*ToolResponse, error)
 245
 246	// OnSourceFunc is called for source references.
 247	OnSourceFunc func(source SourceContent) error
 248
 249	// OnStreamFinishFunc is called when stream finishes.
 250	OnStreamFinishFunc func(usage Usage, finishReason FinishReason, providerMetadata ProviderMetadata) error
 251)
 252
 253// AgentStreamCall represents a streaming call to an agent.
 254type AgentStreamCall struct {
 255	Prompt           string     `json:"prompt"`
 256	Files            []FilePart `json:"files"`
 257	Messages         []Message  `json:"messages"`
 258	MaxOutputTokens  *int64
 259	Temperature      *float64 `json:"temperature"`
 260	TopP             *float64 `json:"top_p"`
 261	TopK             *int64   `json:"top_k"`
 262	PresencePenalty  *float64 `json:"presence_penalty"`
 263	FrequencyPenalty *float64 `json:"frequency_penalty"`
 264	ActiveTools      []string `json:"active_tools"`
 265	Headers          map[string]string
 266	ProviderOptions  ProviderOptions
 267	OnRetry          OnRetryCallback
 268	MaxRetries       *int
 269
 270	StopWhen       []StopCondition
 271	PrepareStep    PrepareStepFunction
 272	RepairToolCall RepairToolCallFunction
 273
 274	// Agent-level callbacks
 275	OnAgentStart  OnAgentStartFunc  // Called when agent starts
 276	OnAgentFinish OnAgentFinishFunc // Called when agent finishes
 277	OnStepStart   OnStepStartFunc   // Called when a step starts
 278	OnStepFinish  OnStepFinishFunc  // Called when a step finishes
 279	OnFinish      OnFinishFunc      // Called when entire agent completes
 280	OnError       OnErrorFunc       // Called when an error occurs
 281
 282	// Stream part callbacks - called for each corresponding stream part type
 283	OnChunk          OnChunkFunc          // Called for each stream part (catch-all)
 284	OnWarnings       OnWarningsFunc       // Called for warnings
 285	OnTextStart      OnTextStartFunc      // Called when text starts
 286	OnTextDelta      OnTextDeltaFunc      // Called for text deltas
 287	OnTextEnd        OnTextEndFunc        // Called when text ends
 288	OnReasoningStart OnReasoningStartFunc // Called when reasoning starts
 289	OnReasoningDelta OnReasoningDeltaFunc // Called for reasoning deltas
 290	OnReasoningEnd   OnReasoningEndFunc   // Called when reasoning ends
 291	OnToolInputStart OnToolInputStartFunc // Called when tool input starts
 292	OnToolInputDelta OnToolInputDeltaFunc // Called for tool input deltas
 293	OnToolInputEnd   OnToolInputEndFunc   // Called when tool input ends
 294	OnToolCall       OnToolCallFunc       // Called when tool call is complete
 295	PreToolExecute   PreToolExecuteFunc   // Called before tool execution (can modify input or block)
 296	OnToolResult     OnToolResultFunc     // Called when tool execution completes
 297	PostToolExecute  PostToolExecuteFunc  // Called after tool execution (can modify output)
 298	OnSource         OnSourceFunc         // Called for source references
 299	OnStreamFinish   OnStreamFinishFunc   // Called when stream finishes
 300}
 301
 302// AgentResult represents the result of an agent execution.
 303type AgentResult struct {
 304	Steps []StepResult
 305	// Final response
 306	Response   Response
 307	TotalUsage Usage
 308}
 309
 310// Agent represents an AI agent that can generate responses and stream responses.
 311type Agent interface {
 312	Generate(context.Context, AgentCall) (*AgentResult, error)
 313	Stream(context.Context, AgentStreamCall) (*AgentResult, error)
 314}
 315
 316// AgentOption defines a function that configures agent settings.
 317type AgentOption = func(*agentSettings)
 318
 319type agent struct {
 320	settings agentSettings
 321}
 322
 323// NewAgent creates a new agent with the given language model and options.
 324func NewAgent(model LanguageModel, opts ...AgentOption) Agent {
 325	settings := agentSettings{
 326		model: model,
 327	}
 328	for _, o := range opts {
 329		o(&settings)
 330	}
 331	return &agent{
 332		settings: settings,
 333	}
 334}
 335
 336func (a *agent) prepareCall(call AgentCall) AgentCall {
 337	call.MaxOutputTokens = cmp.Or(call.MaxOutputTokens, a.settings.maxOutputTokens)
 338	call.Temperature = cmp.Or(call.Temperature, a.settings.temperature)
 339	call.TopP = cmp.Or(call.TopP, a.settings.topP)
 340	call.TopK = cmp.Or(call.TopK, a.settings.topK)
 341	call.PresencePenalty = cmp.Or(call.PresencePenalty, a.settings.presencePenalty)
 342	call.FrequencyPenalty = cmp.Or(call.FrequencyPenalty, a.settings.frequencyPenalty)
 343	call.MaxRetries = cmp.Or(call.MaxRetries, a.settings.maxRetries)
 344
 345	if len(call.StopWhen) == 0 && len(a.settings.stopWhen) > 0 {
 346		call.StopWhen = a.settings.stopWhen
 347	}
 348	if call.PrepareStep == nil && a.settings.prepareStep != nil {
 349		call.PrepareStep = a.settings.prepareStep
 350	}
 351	if call.RepairToolCall == nil && a.settings.repairToolCall != nil {
 352		call.RepairToolCall = a.settings.repairToolCall
 353	}
 354	if call.OnRetry == nil && a.settings.onRetry != nil {
 355		call.OnRetry = a.settings.onRetry
 356	}
 357
 358	providerOptions := ProviderOptions{}
 359	if a.settings.providerOptions != nil {
 360		maps.Copy(providerOptions, a.settings.providerOptions)
 361	}
 362	if call.ProviderOptions != nil {
 363		maps.Copy(providerOptions, call.ProviderOptions)
 364	}
 365	call.ProviderOptions = providerOptions
 366
 367	headers := map[string]string{}
 368
 369	if a.settings.headers != nil {
 370		maps.Copy(headers, a.settings.headers)
 371	}
 372
 373	return call
 374}
 375
 376// Generate implements Agent.
 377func (a *agent) Generate(ctx context.Context, opts AgentCall) (*AgentResult, error) {
 378	opts = a.prepareCall(opts)
 379	initialPrompt, err := a.createPrompt(a.settings.systemPrompt, opts.Prompt, opts.Messages, opts.Files...)
 380	if err != nil {
 381		return nil, err
 382	}
 383	var responseMessages []Message
 384	var steps []StepResult
 385
 386	for {
 387		stepInputMessages := append(initialPrompt, responseMessages...)
 388		stepModel := a.settings.model
 389		stepSystemPrompt := a.settings.systemPrompt
 390		stepActiveTools := opts.ActiveTools
 391		stepToolChoice := ToolChoiceAuto
 392		disableAllTools := false
 393
 394		if opts.PrepareStep != nil {
 395			updatedCtx, prepared, err := opts.PrepareStep(ctx, PrepareStepFunctionOptions{
 396				Model:      stepModel,
 397				Steps:      steps,
 398				StepNumber: len(steps),
 399				Messages:   stepInputMessages,
 400			})
 401			if err != nil {
 402				return nil, err
 403			}
 404
 405			ctx = updatedCtx
 406
 407			// Apply prepared step modifications
 408			if prepared.Messages != nil {
 409				stepInputMessages = prepared.Messages
 410			}
 411			if prepared.Model != nil {
 412				stepModel = prepared.Model
 413			}
 414			if prepared.System != nil {
 415				stepSystemPrompt = *prepared.System
 416			}
 417			if prepared.ToolChoice != nil {
 418				stepToolChoice = *prepared.ToolChoice
 419			}
 420			if len(prepared.ActiveTools) > 0 {
 421				stepActiveTools = prepared.ActiveTools
 422			}
 423			disableAllTools = prepared.DisableAllTools
 424		}
 425
 426		// Recreate prompt with potentially modified system prompt
 427		if stepSystemPrompt != a.settings.systemPrompt {
 428			stepPrompt, err := a.createPrompt(stepSystemPrompt, opts.Prompt, opts.Messages, opts.Files...)
 429			if err != nil {
 430				return nil, err
 431			}
 432			// Replace system message part, keep the rest
 433			if len(stepInputMessages) > 0 && len(stepPrompt) > 0 {
 434				stepInputMessages[0] = stepPrompt[0] // Replace system message
 435			}
 436		}
 437
 438		preparedTools := a.prepareTools(a.settings.tools, stepActiveTools, disableAllTools)
 439
 440		retryOptions := DefaultRetryOptions()
 441		if opts.MaxRetries != nil {
 442			retryOptions.MaxRetries = *opts.MaxRetries
 443		}
 444		retryOptions.OnRetry = opts.OnRetry
 445		retry := RetryWithExponentialBackoffRespectingRetryHeaders[*Response](retryOptions)
 446
 447		result, err := retry(ctx, func() (*Response, error) {
 448			return stepModel.Generate(ctx, Call{
 449				Prompt:           stepInputMessages,
 450				MaxOutputTokens:  opts.MaxOutputTokens,
 451				Temperature:      opts.Temperature,
 452				TopP:             opts.TopP,
 453				TopK:             opts.TopK,
 454				PresencePenalty:  opts.PresencePenalty,
 455				FrequencyPenalty: opts.FrequencyPenalty,
 456				Tools:            preparedTools,
 457				ToolChoice:       &stepToolChoice,
 458				ProviderOptions:  opts.ProviderOptions,
 459			})
 460		})
 461		if err != nil {
 462			return nil, err
 463		}
 464
 465		var stepToolCalls []ToolCallContent
 466		for _, content := range result.Content {
 467			if content.GetType() == ContentTypeToolCall {
 468				toolCall, ok := AsContentType[ToolCallContent](content)
 469				if !ok {
 470					continue
 471				}
 472
 473				// Validate and potentially repair the tool call
 474				validatedToolCall := a.validateAndRepairToolCall(ctx, toolCall, a.settings.tools, stepSystemPrompt, stepInputMessages, a.settings.repairToolCall)
 475				stepToolCalls = append(stepToolCalls, validatedToolCall)
 476			}
 477		}
 478
 479		toolResults, err := a.executeTools(ctx, a.settings.tools, stepToolCalls, nil, nil, nil)
 480
 481		// Build step content with validated tool calls and tool results
 482		stepContent := []Content{}
 483		toolCallIndex := 0
 484		for _, content := range result.Content {
 485			if content.GetType() == ContentTypeToolCall {
 486				// Replace with validated tool call
 487				if toolCallIndex < len(stepToolCalls) {
 488					stepContent = append(stepContent, stepToolCalls[toolCallIndex])
 489					toolCallIndex++
 490				}
 491			} else {
 492				// Keep other content as-is
 493				stepContent = append(stepContent, content)
 494			}
 495		}
 496		// Add tool results
 497		for _, result := range toolResults {
 498			stepContent = append(stepContent, result)
 499		}
 500		currentStepMessages := toResponseMessages(stepContent)
 501		responseMessages = append(responseMessages, currentStepMessages...)
 502
 503		stepResult := StepResult{
 504			Response: Response{
 505				Content:          stepContent,
 506				FinishReason:     result.FinishReason,
 507				Usage:            result.Usage,
 508				Warnings:         result.Warnings,
 509				ProviderMetadata: result.ProviderMetadata,
 510			},
 511			Messages: currentStepMessages,
 512		}
 513		steps = append(steps, stepResult)
 514		shouldStop := isStopConditionMet(opts.StopWhen, steps)
 515
 516		if shouldStop || err != nil || len(stepToolCalls) == 0 || result.FinishReason != FinishReasonToolCalls {
 517			break
 518		}
 519	}
 520
 521	totalUsage := Usage{}
 522
 523	for _, step := range steps {
 524		usage := step.Usage
 525		totalUsage.InputTokens += usage.InputTokens
 526		totalUsage.OutputTokens += usage.OutputTokens
 527		totalUsage.ReasoningTokens += usage.ReasoningTokens
 528		totalUsage.CacheCreationTokens += usage.CacheCreationTokens
 529		totalUsage.CacheReadTokens += usage.CacheReadTokens
 530		totalUsage.TotalTokens += usage.TotalTokens
 531	}
 532
 533	agentResult := &AgentResult{
 534		Steps:      steps,
 535		Response:   steps[len(steps)-1].Response,
 536		TotalUsage: totalUsage,
 537	}
 538	return agentResult, nil
 539}
 540
 541func isStopConditionMet(conditions []StopCondition, steps []StepResult) bool {
 542	if len(conditions) == 0 {
 543		return false
 544	}
 545
 546	for _, condition := range conditions {
 547		if condition(steps) {
 548			return true
 549		}
 550	}
 551	return false
 552}
 553
 554func toResponseMessages(content []Content) []Message {
 555	var assistantParts []MessagePart
 556	var toolParts []MessagePart
 557
 558	for _, c := range content {
 559		switch c.GetType() {
 560		case ContentTypeText:
 561			text, ok := AsContentType[TextContent](c)
 562			if !ok {
 563				continue
 564			}
 565			assistantParts = append(assistantParts, TextPart{
 566				Text:            text.Text,
 567				ProviderOptions: ProviderOptions(text.ProviderMetadata),
 568			})
 569		case ContentTypeReasoning:
 570			reasoning, ok := AsContentType[ReasoningContent](c)
 571			if !ok {
 572				continue
 573			}
 574			assistantParts = append(assistantParts, ReasoningPart{
 575				Text:            reasoning.Text,
 576				ProviderOptions: ProviderOptions(reasoning.ProviderMetadata),
 577			})
 578		case ContentTypeToolCall:
 579			toolCall, ok := AsContentType[ToolCallContent](c)
 580			if !ok {
 581				continue
 582			}
 583			assistantParts = append(assistantParts, ToolCallPart{
 584				ToolCallID:       toolCall.ToolCallID,
 585				ToolName:         toolCall.ToolName,
 586				Input:            toolCall.Input,
 587				ProviderExecuted: toolCall.ProviderExecuted,
 588				ProviderOptions:  ProviderOptions(toolCall.ProviderMetadata),
 589			})
 590		case ContentTypeFile:
 591			file, ok := AsContentType[FileContent](c)
 592			if !ok {
 593				continue
 594			}
 595			assistantParts = append(assistantParts, FilePart{
 596				Data:            file.Data,
 597				MediaType:       file.MediaType,
 598				ProviderOptions: ProviderOptions(file.ProviderMetadata),
 599			})
 600		case ContentTypeSource:
 601			// Sources are metadata about references used to generate the response.
 602			// They don't need to be included in the conversation messages.
 603			continue
 604		case ContentTypeToolResult:
 605			result, ok := AsContentType[ToolResultContent](c)
 606			if !ok {
 607				continue
 608			}
 609			toolParts = append(toolParts, ToolResultPart{
 610				ToolCallID:      result.ToolCallID,
 611				Output:          result.Result,
 612				ProviderOptions: ProviderOptions(result.ProviderMetadata),
 613			})
 614		}
 615	}
 616
 617	var messages []Message
 618	if len(assistantParts) > 0 {
 619		messages = append(messages, Message{
 620			Role:    MessageRoleAssistant,
 621			Content: assistantParts,
 622		})
 623	}
 624	if len(toolParts) > 0 {
 625		messages = append(messages, Message{
 626			Role:    MessageRoleTool,
 627			Content: toolParts,
 628		})
 629	}
 630	return messages
 631}
 632
 633func (a *agent) executeTools(ctx context.Context, allTools []AgentTool, toolCalls []ToolCallContent, toolResultCallback func(result ToolResultContent) error, preToolExecute PreToolExecuteFunc, postToolExecute PostToolExecuteFunc) ([]ToolResultContent, error) {
 634	if len(toolCalls) == 0 {
 635		return nil, nil
 636	}
 637
 638	// Create a map for quick tool lookup
 639	toolMap := make(map[string]AgentTool)
 640	for _, tool := range allTools {
 641		toolMap[tool.Info().Name] = tool
 642	}
 643
 644	// Execute all tool calls sequentially in order
 645	results := make([]ToolResultContent, 0, len(toolCalls))
 646
 647	for _, toolCall := range toolCalls {
 648		// Skip invalid tool calls - create error result
 649		if toolCall.Invalid {
 650			result := ToolResultContent{
 651				ToolCallID: toolCall.ToolCallID,
 652				ToolName:   toolCall.ToolName,
 653				Result: ToolResultOutputContentError{
 654					Error: toolCall.ValidationError,
 655				},
 656				ProviderExecuted: false,
 657			}
 658			results = append(results, result)
 659			if toolResultCallback != nil {
 660				if err := toolResultCallback(result); err != nil {
 661					return nil, err
 662				}
 663			}
 664			continue
 665		}
 666
 667		tool, exists := toolMap[toolCall.ToolName]
 668		if !exists {
 669			result := ToolResultContent{
 670				ToolCallID: toolCall.ToolCallID,
 671				ToolName:   toolCall.ToolName,
 672				Result: ToolResultOutputContentError{
 673					Error: errors.New("Error: Tool not found: " + toolCall.ToolName),
 674				},
 675				ProviderExecuted: false,
 676			}
 677			results = append(results, result)
 678			if toolResultCallback != nil {
 679				if err := toolResultCallback(result); err != nil {
 680					return nil, err
 681				}
 682			}
 683			continue
 684		}
 685
 686		// Prepare tool call for execution
 687		executionToolCall := ToolCall{
 688			ID:    toolCall.ToolCallID,
 689			Name:  toolCall.ToolName,
 690			Input: toolCall.Input,
 691		}
 692
 693		// Call pre-tool execute hook
 694		var preHookErr error
 695		toolCtx := ctx
 696		if preToolExecute != nil {
 697			updatedCtx, modifiedCall, err := preToolExecute(ctx, executionToolCall)
 698			if err != nil {
 699				preHookErr = err
 700			} else {
 701				toolCtx = updatedCtx
 702				if modifiedCall != nil {
 703					executionToolCall = *modifiedCall
 704				}
 705			}
 706		}
 707
 708		// If pre-hook returned error, create error result and skip execution
 709		if preHookErr != nil {
 710			result := ToolResultContent{
 711				ToolCallID: toolCall.ToolCallID,
 712				ToolName:   toolCall.ToolName,
 713				Result: ToolResultOutputContentError{
 714					Error: preHookErr,
 715				},
 716				ProviderExecuted: false,
 717			}
 718			results = append(results, result)
 719			if toolResultCallback != nil {
 720				if err := toolResultCallback(result); err != nil {
 721					return nil, err
 722				}
 723			}
 724			// Continue to next tool call instead of returning error
 725			continue
 726		}
 727
 728		// Execute the tool with timing
 729		startTime := time.Now()
 730		toolResult, err := tool.Run(toolCtx, executionToolCall)
 731		executionTimeMs := time.Since(startTime).Milliseconds()
 732
 733		// Call post-tool execute hook
 734		if postToolExecute != nil && err == nil {
 735			modifiedResponse, postErr := postToolExecute(ctx, executionToolCall, toolResult, executionTimeMs)
 736			if postErr != nil {
 737				// Post-hook error stops execution
 738				result := ToolResultContent{
 739					ToolCallID: toolCall.ToolCallID,
 740					ToolName:   toolCall.ToolName,
 741					Result: ToolResultOutputContentError{
 742						Error: postErr,
 743					},
 744					ClientMetadata:   toolResult.Metadata,
 745					ProviderExecuted: false,
 746				}
 747				if toolResultCallback != nil {
 748					if cbErr := toolResultCallback(result); cbErr != nil {
 749						return nil, cbErr
 750					}
 751				}
 752				return nil, postErr
 753			} else if modifiedResponse != nil {
 754				toolResult = *modifiedResponse
 755			}
 756		}
 757
 758		if err != nil {
 759			result := ToolResultContent{
 760				ToolCallID: toolCall.ToolCallID,
 761				ToolName:   toolCall.ToolName,
 762				Result: ToolResultOutputContentError{
 763					Error: err,
 764				},
 765				ClientMetadata:   toolResult.Metadata,
 766				ProviderExecuted: false,
 767			}
 768			if toolResultCallback != nil {
 769				if cbErr := toolResultCallback(result); cbErr != nil {
 770					return nil, cbErr
 771				}
 772			}
 773			return nil, err
 774		}
 775
 776		var result ToolResultContent
 777		if toolResult.IsError {
 778			result = ToolResultContent{
 779				ToolCallID: toolCall.ToolCallID,
 780				ToolName:   toolCall.ToolName,
 781				Result: ToolResultOutputContentError{
 782					Error: errors.New(toolResult.Content),
 783				},
 784				ClientMetadata:   toolResult.Metadata,
 785				ProviderExecuted: false,
 786			}
 787		} else {
 788			result = ToolResultContent{
 789				ToolCallID: toolCall.ToolCallID,
 790				ToolName:   toolCall.ToolName,
 791				Result: ToolResultOutputContentText{
 792					Text: toolResult.Content,
 793				},
 794				ClientMetadata:   toolResult.Metadata,
 795				ProviderExecuted: false,
 796			}
 797		}
 798		results = append(results, result)
 799		if toolResultCallback != nil {
 800			if err := toolResultCallback(result); err != nil {
 801				return nil, err
 802			}
 803		}
 804	}
 805
 806	return results, nil
 807}
 808
 809// Stream implements Agent.
 810func (a *agent) Stream(ctx context.Context, opts AgentStreamCall) (*AgentResult, error) {
 811	// Convert AgentStreamCall to AgentCall for preparation
 812	call := AgentCall{
 813		Prompt:           opts.Prompt,
 814		Files:            opts.Files,
 815		Messages:         opts.Messages,
 816		MaxOutputTokens:  opts.MaxOutputTokens,
 817		Temperature:      opts.Temperature,
 818		TopP:             opts.TopP,
 819		TopK:             opts.TopK,
 820		PresencePenalty:  opts.PresencePenalty,
 821		FrequencyPenalty: opts.FrequencyPenalty,
 822		ActiveTools:      opts.ActiveTools,
 823		ProviderOptions:  opts.ProviderOptions,
 824		MaxRetries:       opts.MaxRetries,
 825		OnRetry:          opts.OnRetry,
 826		StopWhen:         opts.StopWhen,
 827		PrepareStep:      opts.PrepareStep,
 828		RepairToolCall:   opts.RepairToolCall,
 829	}
 830
 831	call = a.prepareCall(call)
 832
 833	initialPrompt, err := a.createPrompt(a.settings.systemPrompt, call.Prompt, call.Messages, call.Files...)
 834	if err != nil {
 835		return nil, err
 836	}
 837
 838	var responseMessages []Message
 839	var steps []StepResult
 840	var totalUsage Usage
 841
 842	// Start agent stream
 843	if opts.OnAgentStart != nil {
 844		opts.OnAgentStart()
 845	}
 846
 847	for stepNumber := 0; ; stepNumber++ {
 848		stepInputMessages := append(initialPrompt, responseMessages...)
 849		stepModel := a.settings.model
 850		stepSystemPrompt := a.settings.systemPrompt
 851		stepActiveTools := call.ActiveTools
 852		stepToolChoice := ToolChoiceAuto
 853		disableAllTools := false
 854
 855		// Apply step preparation if provided
 856		if call.PrepareStep != nil {
 857			updatedCtx, prepared, err := call.PrepareStep(ctx, PrepareStepFunctionOptions{
 858				Model:      stepModel,
 859				Steps:      steps,
 860				StepNumber: stepNumber,
 861				Messages:   stepInputMessages,
 862			})
 863			if err != nil {
 864				return nil, err
 865			}
 866
 867			ctx = updatedCtx
 868
 869			if prepared.Messages != nil {
 870				stepInputMessages = prepared.Messages
 871			}
 872			if prepared.Model != nil {
 873				stepModel = prepared.Model
 874			}
 875			if prepared.System != nil {
 876				stepSystemPrompt = *prepared.System
 877			}
 878			if prepared.ToolChoice != nil {
 879				stepToolChoice = *prepared.ToolChoice
 880			}
 881			if len(prepared.ActiveTools) > 0 {
 882				stepActiveTools = prepared.ActiveTools
 883			}
 884			disableAllTools = prepared.DisableAllTools
 885		}
 886
 887		// Recreate prompt with potentially modified system prompt
 888		if stepSystemPrompt != a.settings.systemPrompt {
 889			stepPrompt, err := a.createPrompt(stepSystemPrompt, call.Prompt, call.Messages, call.Files...)
 890			if err != nil {
 891				return nil, err
 892			}
 893			if len(stepInputMessages) > 0 && len(stepPrompt) > 0 {
 894				stepInputMessages[0] = stepPrompt[0]
 895			}
 896		}
 897
 898		preparedTools := a.prepareTools(a.settings.tools, stepActiveTools, disableAllTools)
 899
 900		// Start step stream
 901		if opts.OnStepStart != nil {
 902			_ = opts.OnStepStart(stepNumber)
 903		}
 904
 905		// Create streaming call
 906		streamCall := Call{
 907			Prompt:           stepInputMessages,
 908			MaxOutputTokens:  call.MaxOutputTokens,
 909			Temperature:      call.Temperature,
 910			TopP:             call.TopP,
 911			TopK:             call.TopK,
 912			PresencePenalty:  call.PresencePenalty,
 913			FrequencyPenalty: call.FrequencyPenalty,
 914			Tools:            preparedTools,
 915			ToolChoice:       &stepToolChoice,
 916			ProviderOptions:  call.ProviderOptions,
 917		}
 918
 919		// Execute step with retry logic wrapping both stream creation and processing
 920		retryOptions := DefaultRetryOptions()
 921		if call.MaxRetries != nil {
 922			retryOptions.MaxRetries = *call.MaxRetries
 923		}
 924		retryOptions.OnRetry = call.OnRetry
 925		retry := RetryWithExponentialBackoffRespectingRetryHeaders[stepExecutionResult](retryOptions)
 926
 927		result, err := retry(ctx, func() (stepExecutionResult, error) {
 928			// Create the stream
 929			stream, err := stepModel.Stream(ctx, streamCall)
 930			if err != nil {
 931				return stepExecutionResult{}, err
 932			}
 933
 934			// Process the stream
 935			result, err := a.processStepStream(ctx, stream, opts, steps)
 936			if err != nil {
 937				return stepExecutionResult{}, err
 938			}
 939
 940			return result, nil
 941		})
 942		if err != nil {
 943			if opts.OnError != nil {
 944				opts.OnError(err)
 945			}
 946			return nil, err
 947		}
 948
 949		steps = append(steps, result.StepResult)
 950		totalUsage = addUsage(totalUsage, result.StepResult.Usage)
 951
 952		// Call step finished callback
 953		if opts.OnStepFinish != nil {
 954			_ = opts.OnStepFinish(result.StepResult)
 955		}
 956
 957		// Add step messages to response messages
 958		stepMessages := toResponseMessages(result.StepResult.Content)
 959		responseMessages = append(responseMessages, stepMessages...)
 960
 961		// Check stop conditions
 962		shouldStop := isStopConditionMet(call.StopWhen, steps)
 963		if shouldStop || !result.ShouldContinue {
 964			break
 965		}
 966	}
 967
 968	// Finish agent stream
 969	agentResult := &AgentResult{
 970		Steps:      steps,
 971		Response:   steps[len(steps)-1].Response,
 972		TotalUsage: totalUsage,
 973	}
 974
 975	if opts.OnFinish != nil {
 976		opts.OnFinish(agentResult)
 977	}
 978
 979	if opts.OnAgentFinish != nil {
 980		_ = opts.OnAgentFinish(agentResult)
 981	}
 982
 983	return agentResult, nil
 984}
 985
 986func (a *agent) prepareTools(tools []AgentTool, activeTools []string, disableAllTools bool) []Tool {
 987	preparedTools := make([]Tool, 0, len(tools))
 988
 989	// If explicitly disabling all tools, return no tools
 990	if disableAllTools {
 991		return preparedTools
 992	}
 993
 994	for _, tool := range tools {
 995		// If activeTools has items, only include tools in the list
 996		// If activeTools is empty, include all tools
 997		if len(activeTools) > 0 && !slices.Contains(activeTools, tool.Info().Name) {
 998			continue
 999		}
1000		info := tool.Info()
1001		preparedTools = append(preparedTools, FunctionTool{
1002			Name:        info.Name,
1003			Description: info.Description,
1004			InputSchema: map[string]any{
1005				"type":       "object",
1006				"properties": info.Parameters,
1007				"required":   info.Required,
1008			},
1009			ProviderOptions: tool.ProviderOptions(),
1010		})
1011	}
1012	return preparedTools
1013}
1014
1015// validateAndRepairToolCall validates a tool call and attempts repair if validation fails.
1016func (a *agent) validateAndRepairToolCall(ctx context.Context, toolCall ToolCallContent, availableTools []AgentTool, systemPrompt string, messages []Message, repairFunc RepairToolCallFunction) ToolCallContent {
1017	if err := a.validateToolCall(toolCall, availableTools); err == nil {
1018		return toolCall
1019	} else { //nolint: revive
1020		if repairFunc != nil {
1021			repairOptions := ToolCallRepairOptions{
1022				OriginalToolCall: toolCall,
1023				ValidationError:  err,
1024				AvailableTools:   availableTools,
1025				SystemPrompt:     systemPrompt,
1026				Messages:         messages,
1027			}
1028
1029			if repairedToolCall, repairErr := repairFunc(ctx, repairOptions); repairErr == nil && repairedToolCall != nil {
1030				if validateErr := a.validateToolCall(*repairedToolCall, availableTools); validateErr == nil {
1031					return *repairedToolCall
1032				}
1033			}
1034		}
1035
1036		invalidToolCall := toolCall
1037		invalidToolCall.Invalid = true
1038		invalidToolCall.ValidationError = err
1039		return invalidToolCall
1040	}
1041}
1042
1043// validateToolCall validates a tool call against available tools and their schemas.
1044func (a *agent) validateToolCall(toolCall ToolCallContent, availableTools []AgentTool) error {
1045	var tool AgentTool
1046	for _, t := range availableTools {
1047		if t.Info().Name == toolCall.ToolName {
1048			tool = t
1049			break
1050		}
1051	}
1052
1053	if tool == nil {
1054		return fmt.Errorf("tool not found: %s", toolCall.ToolName)
1055	}
1056
1057	// Validate JSON parsing
1058	var input map[string]any
1059	if err := json.Unmarshal([]byte(toolCall.Input), &input); err != nil {
1060		return fmt.Errorf("invalid JSON input: %w", err)
1061	}
1062
1063	// Basic schema validation (check required fields)
1064	// TODO: more robust schema validation using JSON Schema or similar
1065	toolInfo := tool.Info()
1066	for _, required := range toolInfo.Required {
1067		if _, exists := input[required]; !exists {
1068			return fmt.Errorf("missing required parameter: %s", required)
1069		}
1070	}
1071	return nil
1072}
1073
1074func (a *agent) createPrompt(system, prompt string, messages []Message, files ...FilePart) (Prompt, error) {
1075	if prompt == "" {
1076		return nil, &Error{Title: "invalid argument", Message: "prompt can't be empty"}
1077	}
1078
1079	var preparedPrompt Prompt
1080
1081	if system != "" {
1082		preparedPrompt = append(preparedPrompt, NewSystemMessage(system))
1083	}
1084	preparedPrompt = append(preparedPrompt, messages...)
1085	preparedPrompt = append(preparedPrompt, NewUserMessage(prompt, files...))
1086	return preparedPrompt, nil
1087}
1088
1089// WithSystemPrompt sets the system prompt for the agent.
1090func WithSystemPrompt(prompt string) AgentOption {
1091	return func(s *agentSettings) {
1092		s.systemPrompt = prompt
1093	}
1094}
1095
1096// WithMaxOutputTokens sets the maximum output tokens for the agent.
1097func WithMaxOutputTokens(tokens int64) AgentOption {
1098	return func(s *agentSettings) {
1099		s.maxOutputTokens = &tokens
1100	}
1101}
1102
1103// WithTemperature sets the temperature for the agent.
1104func WithTemperature(temp float64) AgentOption {
1105	return func(s *agentSettings) {
1106		s.temperature = &temp
1107	}
1108}
1109
1110// WithTopP sets the top-p value for the agent.
1111func WithTopP(topP float64) AgentOption {
1112	return func(s *agentSettings) {
1113		s.topP = &topP
1114	}
1115}
1116
1117// WithTopK sets the top-k value for the agent.
1118func WithTopK(topK int64) AgentOption {
1119	return func(s *agentSettings) {
1120		s.topK = &topK
1121	}
1122}
1123
1124// WithPresencePenalty sets the presence penalty for the agent.
1125func WithPresencePenalty(penalty float64) AgentOption {
1126	return func(s *agentSettings) {
1127		s.presencePenalty = &penalty
1128	}
1129}
1130
1131// WithFrequencyPenalty sets the frequency penalty for the agent.
1132func WithFrequencyPenalty(penalty float64) AgentOption {
1133	return func(s *agentSettings) {
1134		s.frequencyPenalty = &penalty
1135	}
1136}
1137
1138// WithTools sets the tools for the agent.
1139func WithTools(tools ...AgentTool) AgentOption {
1140	return func(s *agentSettings) {
1141		s.tools = append(s.tools, tools...)
1142	}
1143}
1144
1145// WithStopConditions sets the stop conditions for the agent.
1146func WithStopConditions(conditions ...StopCondition) AgentOption {
1147	return func(s *agentSettings) {
1148		s.stopWhen = append(s.stopWhen, conditions...)
1149	}
1150}
1151
1152// WithPrepareStep sets the prepare step function for the agent.
1153func WithPrepareStep(fn PrepareStepFunction) AgentOption {
1154	return func(s *agentSettings) {
1155		s.prepareStep = fn
1156	}
1157}
1158
1159// WithRepairToolCall sets the repair tool call function for the agent.
1160func WithRepairToolCall(fn RepairToolCallFunction) AgentOption {
1161	return func(s *agentSettings) {
1162		s.repairToolCall = fn
1163	}
1164}
1165
1166// WithMaxRetries sets the maximum number of retries for the agent.
1167func WithMaxRetries(maxRetries int) AgentOption {
1168	return func(s *agentSettings) {
1169		s.maxRetries = &maxRetries
1170	}
1171}
1172
1173// WithOnRetry sets the retry callback for the agent.
1174func WithOnRetry(callback OnRetryCallback) AgentOption {
1175	return func(s *agentSettings) {
1176		s.onRetry = callback
1177	}
1178}
1179
1180// processStepStream processes a single step's stream and returns the step result.
1181func (a *agent) processStepStream(ctx context.Context, stream StreamResponse, opts AgentStreamCall, _ []StepResult) (stepExecutionResult, error) {
1182	var stepContent []Content
1183	var stepToolCalls []ToolCallContent
1184	var stepUsage Usage
1185	stepFinishReason := FinishReasonUnknown
1186	var stepWarnings []CallWarning
1187	var stepProviderMetadata ProviderMetadata
1188
1189	activeToolCalls := make(map[string]*ToolCallContent)
1190	activeTextContent := make(map[string]string)
1191	type reasoningContent struct {
1192		content string
1193		options ProviderMetadata
1194	}
1195	activeReasoningContent := make(map[string]reasoningContent)
1196
1197	// Process stream parts
1198	for part := range stream {
1199		// Forward all parts to chunk callback
1200		if opts.OnChunk != nil {
1201			err := opts.OnChunk(part)
1202			if err != nil {
1203				return stepExecutionResult{}, err
1204			}
1205		}
1206
1207		switch part.Type {
1208		case StreamPartTypeWarnings:
1209			stepWarnings = part.Warnings
1210			if opts.OnWarnings != nil {
1211				err := opts.OnWarnings(part.Warnings)
1212				if err != nil {
1213					return stepExecutionResult{}, err
1214				}
1215			}
1216
1217		case StreamPartTypeTextStart:
1218			activeTextContent[part.ID] = ""
1219			if opts.OnTextStart != nil {
1220				err := opts.OnTextStart(part.ID)
1221				if err != nil {
1222					return stepExecutionResult{}, err
1223				}
1224			}
1225
1226		case StreamPartTypeTextDelta:
1227			if _, exists := activeTextContent[part.ID]; exists {
1228				activeTextContent[part.ID] += part.Delta
1229			}
1230			if opts.OnTextDelta != nil {
1231				err := opts.OnTextDelta(part.ID, part.Delta)
1232				if err != nil {
1233					return stepExecutionResult{}, err
1234				}
1235			}
1236
1237		case StreamPartTypeTextEnd:
1238			if text, exists := activeTextContent[part.ID]; exists {
1239				stepContent = append(stepContent, TextContent{
1240					Text:             text,
1241					ProviderMetadata: part.ProviderMetadata,
1242				})
1243				delete(activeTextContent, part.ID)
1244			}
1245			if opts.OnTextEnd != nil {
1246				err := opts.OnTextEnd(part.ID)
1247				if err != nil {
1248					return stepExecutionResult{}, err
1249				}
1250			}
1251
1252		case StreamPartTypeReasoningStart:
1253			activeReasoningContent[part.ID] = reasoningContent{content: part.Delta, options: part.ProviderMetadata}
1254			if opts.OnReasoningStart != nil {
1255				content := ReasoningContent{
1256					Text:             part.Delta,
1257					ProviderMetadata: part.ProviderMetadata,
1258				}
1259				err := opts.OnReasoningStart(part.ID, content)
1260				if err != nil {
1261					return stepExecutionResult{}, err
1262				}
1263			}
1264
1265		case StreamPartTypeReasoningDelta:
1266			if active, exists := activeReasoningContent[part.ID]; exists {
1267				active.content += part.Delta
1268				active.options = part.ProviderMetadata
1269				activeReasoningContent[part.ID] = active
1270			}
1271			if opts.OnReasoningDelta != nil {
1272				err := opts.OnReasoningDelta(part.ID, part.Delta)
1273				if err != nil {
1274					return stepExecutionResult{}, err
1275				}
1276			}
1277
1278		case StreamPartTypeReasoningEnd:
1279			if active, exists := activeReasoningContent[part.ID]; exists {
1280				if part.ProviderMetadata != nil {
1281					active.options = part.ProviderMetadata
1282				}
1283				content := ReasoningContent{
1284					Text:             active.content,
1285					ProviderMetadata: active.options,
1286				}
1287				stepContent = append(stepContent, content)
1288				if opts.OnReasoningEnd != nil {
1289					err := opts.OnReasoningEnd(part.ID, content)
1290					if err != nil {
1291						return stepExecutionResult{}, err
1292					}
1293				}
1294				delete(activeReasoningContent, part.ID)
1295			}
1296
1297		case StreamPartTypeToolInputStart:
1298			activeToolCalls[part.ID] = &ToolCallContent{
1299				ToolCallID:       part.ID,
1300				ToolName:         part.ToolCallName,
1301				Input:            "",
1302				ProviderExecuted: part.ProviderExecuted,
1303			}
1304			if opts.OnToolInputStart != nil {
1305				err := opts.OnToolInputStart(part.ID, part.ToolCallName)
1306				if err != nil {
1307					return stepExecutionResult{}, err
1308				}
1309			}
1310
1311		case StreamPartTypeToolInputDelta:
1312			if toolCall, exists := activeToolCalls[part.ID]; exists {
1313				toolCall.Input += part.Delta
1314			}
1315			if opts.OnToolInputDelta != nil {
1316				err := opts.OnToolInputDelta(part.ID, part.Delta)
1317				if err != nil {
1318					return stepExecutionResult{}, err
1319				}
1320			}
1321
1322		case StreamPartTypeToolInputEnd:
1323			if opts.OnToolInputEnd != nil {
1324				err := opts.OnToolInputEnd(part.ID)
1325				if err != nil {
1326					return stepExecutionResult{}, err
1327				}
1328			}
1329
1330		case StreamPartTypeToolCall:
1331			toolCall := ToolCallContent{
1332				ToolCallID:       part.ID,
1333				ToolName:         part.ToolCallName,
1334				Input:            part.ToolCallInput,
1335				ProviderExecuted: part.ProviderExecuted,
1336				ProviderMetadata: part.ProviderMetadata,
1337			}
1338
1339			// Validate and potentially repair the tool call
1340			validatedToolCall := a.validateAndRepairToolCall(ctx, toolCall, a.settings.tools, a.settings.systemPrompt, nil, opts.RepairToolCall)
1341			stepToolCalls = append(stepToolCalls, validatedToolCall)
1342			stepContent = append(stepContent, validatedToolCall)
1343
1344			if opts.OnToolCall != nil {
1345				err := opts.OnToolCall(validatedToolCall)
1346				if err != nil {
1347					return stepExecutionResult{}, err
1348				}
1349			}
1350
1351			// Clean up active tool call
1352			delete(activeToolCalls, part.ID)
1353
1354		case StreamPartTypeSource:
1355			sourceContent := SourceContent{
1356				SourceType:       part.SourceType,
1357				ID:               part.ID,
1358				URL:              part.URL,
1359				Title:            part.Title,
1360				ProviderMetadata: part.ProviderMetadata,
1361			}
1362			stepContent = append(stepContent, sourceContent)
1363			if opts.OnSource != nil {
1364				err := opts.OnSource(sourceContent)
1365				if err != nil {
1366					return stepExecutionResult{}, err
1367				}
1368			}
1369
1370		case StreamPartTypeFinish:
1371			stepUsage = part.Usage
1372			stepFinishReason = part.FinishReason
1373			stepProviderMetadata = part.ProviderMetadata
1374			if opts.OnStreamFinish != nil {
1375				err := opts.OnStreamFinish(part.Usage, part.FinishReason, part.ProviderMetadata)
1376				if err != nil {
1377					return stepExecutionResult{}, err
1378				}
1379			}
1380
1381		case StreamPartTypeError:
1382			return stepExecutionResult{}, part.Error
1383		}
1384	}
1385
1386	// Execute tools if any
1387	var toolResults []ToolResultContent
1388	if len(stepToolCalls) > 0 {
1389		var err error
1390		toolResults, err = a.executeTools(ctx, a.settings.tools, stepToolCalls, opts.OnToolResult, opts.PreToolExecute, opts.PostToolExecute)
1391		if err != nil {
1392			return stepExecutionResult{}, err
1393		}
1394		// Add tool results to content
1395		for _, result := range toolResults {
1396			stepContent = append(stepContent, result)
1397		}
1398	}
1399
1400	stepResult := StepResult{
1401		Response: Response{
1402			Content:          stepContent,
1403			FinishReason:     stepFinishReason,
1404			Usage:            stepUsage,
1405			Warnings:         stepWarnings,
1406			ProviderMetadata: stepProviderMetadata,
1407		},
1408		Messages: toResponseMessages(stepContent),
1409	}
1410
1411	// Determine if we should continue (has tool calls and not stopped)
1412	shouldContinue := len(stepToolCalls) > 0 && stepFinishReason == FinishReasonToolCalls
1413
1414	return stepExecutionResult{
1415		StepResult:     stepResult,
1416		ShouldContinue: shouldContinue,
1417	}, nil
1418}
1419
1420func addUsage(a, b Usage) Usage {
1421	return Usage{
1422		InputTokens:         a.InputTokens + b.InputTokens,
1423		OutputTokens:        a.OutputTokens + b.OutputTokens,
1424		TotalTokens:         a.TotalTokens + b.TotalTokens,
1425		ReasoningTokens:     a.ReasoningTokens + b.ReasoningTokens,
1426		CacheCreationTokens: a.CacheCreationTokens + b.CacheCreationTokens,
1427		CacheReadTokens:     a.CacheReadTokens + b.CacheReadTokens,
1428	}
1429}
1430
1431// WithHeaders sets the headers for the agent.
1432func WithHeaders(headers map[string]string) AgentOption {
1433	return func(s *agentSettings) {
1434		s.headers = headers
1435	}
1436}
1437
1438// WithProviderOptions sets the provider options for the agent.
1439func WithProviderOptions(providerOptions ProviderOptions) AgentOption {
1440	return func(s *agentSettings) {
1441		s.providerOptions = providerOptions
1442	}
1443}