feat: JIT-injectable toolsets with load_toolset tool

Implement dynamic tool loading system that allows tools to be loaded
on-demand rather than included in the default set.

Key changes:
- Add toolsets module with registry of loadable toolsets
- Add load_toolset tool that returns tool definitions for a named toolset
- Add <available_toolsets> section to system prompt
- Track loaded toolsets in Agent, extend tool definitions dynamically
- Move webdriver (15 tools) to JIT-only loading

Benefits:
- Leaner default context (fewer tokens consumed)
- On-demand loading when agent needs specialized tools
- Extensible registry for future toolsets
- Idempotent loading with helpful error messages

Files:
- crates/g3-core/src/toolsets.rs (new)
- crates/g3-core/src/tools/toolsets.rs (new)
- crates/g3-core/src/tool_definitions.rs
- crates/g3-core/src/tool_dispatch.rs
- crates/g3-core/src/prompts.rs
- crates/g3-core/src/lib.rs
- crates/g3-core/src/tools/executor.rs
This commit is contained in:
Dhanji R. Prasanna
2026-02-06 09:35:11 +11:00
parent ff15db44c0
commit cbced3390c
9 changed files with 587 additions and 228 deletions

View File

@@ -24,6 +24,7 @@ pub mod ui_writer;
pub mod utils;
pub mod webdriver_session;
pub mod skills;
pub mod toolsets;
pub use feedback_extraction::{
extract_coach_feedback, ExtractedFeedback, FeedbackExtractionConfig, FeedbackSource,
@@ -163,6 +164,8 @@ pub struct Agent<W: UiWriter> {
in_plan_mode: bool,
/// Manager for async research tasks
pending_research_manager: pending_research::PendingResearchManager,
/// Set of toolset names that have been loaded in this session
loaded_toolsets: std::collections::HashSet<String>,
}
impl<W: UiWriter> Agent<W> {
@@ -218,6 +221,7 @@ impl<W: UiWriter> Agent<W> {
acd_enabled: false,
in_plan_mode: false,
pending_research_manager: pending_research::PendingResearchManager::new(),
loaded_toolsets: std::collections::HashSet::new(),
}
}
@@ -614,6 +618,26 @@ impl<W: UiWriter> Agent<W> {
provider_config::resolve_temperature(&self.config, provider_name)
}
/// Get tool definitions including any dynamically loaded toolsets.
fn get_tool_definitions_with_loaded_toolsets(&self, tool_config: tool_definitions::ToolConfig) -> Vec<g3_providers::Tool> {
let mut tools = tool_definitions::create_tool_definitions(tool_config);
// Add tools from loaded toolsets
for toolset_name in &self.loaded_toolsets {
if let Ok(toolset) = toolsets::get_toolset(toolset_name) {
let toolset_tools = toolset.get_tools();
// Avoid duplicates (in case toolset was already in base config)
for tool in toolset_tools {
if !tools.iter().any(|t| t.name == tool.name) {
tools.push(tool);
}
}
}
}
tools
}
/// Print provider diagnostics through the UiWriter for visibility
pub fn print_provider_banner(&self, role_label: &str) {
if let Ok((provider_name, model)) = self.get_provider_info() {
@@ -1016,10 +1040,9 @@ impl<W: UiWriter> Agent<W> {
let _supports_cache_control = provider.supports_cache_control();
let tools = if provider.has_native_tool_calling() {
let tool_config = tool_definitions::ToolConfig::new(
self.config.webdriver.enabled,
self.config.computer_control.enabled,
);
Some(tool_definitions::create_tool_definitions(tool_config))
Some(self.get_tool_definitions_with_loaded_toolsets(tool_config))
} else {
None
};
@@ -1887,10 +1910,9 @@ Skip if nothing new. Be brief."#;
let provider_name = provider.name().to_string();
let tools = if provider.has_native_tool_calling() {
let tool_config = tool_definitions::ToolConfig::new(
self.config.webdriver.enabled,
self.config.computer_control.enabled,
);
Some(tool_definitions::create_tool_definitions(tool_config))
Some(self.get_tool_definitions_with_loaded_toolsets(tool_config))
} else {
None
};
@@ -2569,11 +2591,10 @@ Skip if nothing new. Be brief."#;
let provider_for_tools = self.providers.get(None)?;
if provider_for_tools.has_native_tool_calling() {
let tool_config = tool_definitions::ToolConfig::new(
self.config.webdriver.enabled,
self.config.computer_control.enabled,
);
request.tools =
Some(tool_definitions::create_tool_definitions(tool_config));
Some(self.get_tool_definitions_with_loaded_toolsets(tool_config));
}
// DO NOT add final_display_content to full_response here!
@@ -2981,6 +3002,7 @@ Skip if nothing new. Be brief."#;
requirements_sha: self.requirements_sha.as_deref(),
context_total_tokens: self.context_window.total_tokens,
context_used_tokens: self.context_window.used_tokens,
loaded_toolsets: &mut self.loaded_toolsets,
};
// Dispatch to the appropriate tool handler