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}