Build creates the final Inventory with all configuration applied. This processes toolset filtering, tool name resolution, and sets up the inventory for use. The returned Inventory is ready for use with AvailableTools(), RegisterAll(), etc. Build returns an error if any tools specified via WithTools
()
| 210 | // (i.e., they don't exist in the tool set and are not deprecated aliases). |
| 211 | // This ensures invalid tool configurations fail fast at build time. |
| 212 | func (b *Builder) Build() (*Inventory, error) { |
| 213 | tools := b.tools |
| 214 | |
| 215 | // Install the feature-flag filter at the head of the pipeline so that |
| 216 | // flag-gated tools are excluded before any user-supplied WithFilter sees |
| 217 | // them. Doing this in Build() (rather than inside WithFeatureChecker) |
| 218 | // keeps the install idempotent — repeated WithFeatureChecker calls |
| 219 | // replace the checker without stacking duplicate filters. |
| 220 | filters := b.filters |
| 221 | if b.featureChecker != nil { |
| 222 | filters = append([]ToolFilter{createFeatureFlagFilter(b.featureChecker)}, filters...) |
| 223 | } |
| 224 | |
| 225 | r := &Inventory{ |
| 226 | tools: tools, |
| 227 | resourceTemplates: b.resourceTemplates, |
| 228 | prompts: b.prompts, |
| 229 | deprecatedAliases: b.deprecatedAliases, |
| 230 | readOnly: b.readOnly, |
| 231 | featureChecker: b.featureChecker, |
| 232 | filters: filters, |
| 233 | } |
| 234 | |
| 235 | // Process toolsets and pre-compute metadata in a single pass |
| 236 | r.enabledToolsets, r.unrecognizedToolsets, r.toolsetIDs, r.toolsetIDSet, r.defaultToolsetIDs, r.toolsetDescriptions = b.processToolsets() |
| 237 | |
| 238 | // Build set of valid tool names for validation |
| 239 | validToolNames := make(map[string]bool, len(tools)) |
| 240 | for i := range tools { |
| 241 | validToolNames[tools[i].Tool.Name] = true |
| 242 | } |
| 243 | |
| 244 | // Process additional tools (clean, resolve aliases, and track unrecognized) |
| 245 | if len(b.additionalTools) > 0 { |
| 246 | cleanedTools := cleanTools(b.additionalTools) |
| 247 | |
| 248 | r.additionalTools = make(map[string]bool, len(cleanedTools)) |
| 249 | var unrecognizedTools []string |
| 250 | for _, name := range cleanedTools { |
| 251 | // Always include the original name - this handles the case where |
| 252 | // the tool exists but is controlled by a feature flag that's OFF. |
| 253 | r.additionalTools[name] = true |
| 254 | // Also include the canonical name if this is a deprecated alias. |
| 255 | // This handles the case where the feature flag is ON and only |
| 256 | // the new consolidated tool is available. |
| 257 | if canonical, isAlias := b.deprecatedAliases[name]; isAlias { |
| 258 | r.additionalTools[canonical] = true |
| 259 | } else if !validToolNames[name] { |
| 260 | // Not a valid tool and not a deprecated alias - track as unrecognized |
| 261 | unrecognizedTools = append(unrecognizedTools, name) |
| 262 | } |
| 263 | } |
| 264 | |
| 265 | // Error out if there are unrecognized tools |
| 266 | if len(unrecognizedTools) > 0 { |
| 267 | return nil, fmt.Errorf("%w: %s", ErrUnknownTools, strings.Join(unrecognizedTools, ", ")) |
| 268 | } |
| 269 | } |