getToolsForAgent returns the tool definitions for an agent based on its configuration. Toolset instructions support ${...} JavaScript placeholders (e.g. ${env.X}); they are expanded here using the runtime env provider.
(ctx context.Context, a *latest.AgentConfig, parentDir string, runConfig *config.RuntimeConfig, registry ToolsetRegistry, configName string, expander *js.Expander)
| 667 | // configuration. Toolset instructions support ${...} JavaScript placeholders |
| 668 | // (e.g. ${env.X}); they are expanded here using the runtime env provider. |
| 669 | func getToolsForAgent(ctx context.Context, a *latest.AgentConfig, parentDir string, runConfig *config.RuntimeConfig, registry ToolsetRegistry, configName string, expander *js.Expander) ([]tools.ToolSet, []string) { |
| 670 | var ( |
| 671 | toolSets []tools.ToolSet |
| 672 | warnings []string |
| 673 | lspBackends []lsp.Backend |
| 674 | ) |
| 675 | |
| 676 | deferredToolset := deferred.New() |
| 677 | |
| 678 | for i := range a.Toolsets { |
| 679 | toolset := a.Toolsets[i] |
| 680 | |
| 681 | tool, err := registry.CreateTool(ctx, toolset, parentDir, runConfig, configName) |
| 682 | if err != nil { |
| 683 | // Collect error but continue loading other toolsets |
| 684 | slog.WarnContext(ctx, "Toolset configuration failed; skipping", "type", toolset.Type, "ref", toolset.Ref, "command", toolset.Command, "error", err) |
| 685 | warnings = append(warnings, fmt.Sprintf("toolset %s failed: %v", toolset.Type, err)) |
| 686 | continue |
| 687 | } |
| 688 | |
| 689 | wrapped := WithToolsFilter(tool, toolset.Tools...) |
| 690 | wrapped = WithReadOnlyFilter(wrapped, toolset.ReadOnly || a.ReadOnly) |
| 691 | wrapped = WithInstructions(wrapped, expander.Expand(ctx, toolset.Instruction, nil)) |
| 692 | wrapped = WithToon(wrapped, toolset.Toon) |
| 693 | wrapped = WithModelOverride(wrapped, toolset.Model) |
| 694 | |
| 695 | // Handle deferred tools |
| 696 | if !toolset.Defer.IsEmpty() { |
| 697 | deferredToolset.AddSource(wrapped, toolset.Defer.DeferAll, toolset.Defer.Tools) |
| 698 | if toolset.Defer.DeferAll { |
| 699 | // Don't add the wrapped toolset to toolSets - all its tools are deferred |
| 700 | // TODO: maybe we _do_ want to add this toolset since it has instructions? |
| 701 | continue |
| 702 | } else { |
| 703 | wrapped = WithToolsExcludeFilter(wrapped, toolset.Defer.Tools...) |
| 704 | } |
| 705 | } |
| 706 | |
| 707 | // Collect LSP backends for multiplexing when there are multiple. |
| 708 | // Instead of adding them individually (which causes duplicate tool names), |
| 709 | // they are combined into a single Multiplexer after the loop. |
| 710 | if toolset.Type == "lsp" { |
| 711 | if lspTool, ok := tool.(*lsp.ToolSet); ok { |
| 712 | lspBackends = append(lspBackends, lsp.Backend{LSP: lspTool, Toolset: wrapped}) |
| 713 | continue |
| 714 | } |
| 715 | slog.WarnContext(ctx, "Toolset configured as type 'lsp' but registry returned unexpected type; treating as regular toolset", |
| 716 | "type", fmt.Sprintf("%T", tool), "command", toolset.Command) |
| 717 | } |
| 718 | |
| 719 | toolSets = append(toolSets, wrapped) |
| 720 | } |
| 721 | |
| 722 | // Merge LSP backends: if there are multiple, combine them into a single |
| 723 | // multiplexer so the LLM sees one set of lsp_* tools instead of duplicates. |
| 724 | if len(lspBackends) > 1 { |
| 725 | toolSets = append(toolSets, lsp.NewLSPMultiplexer(lspBackends)) |
| 726 | } else if len(lspBackends) == 1 { |