feat(runtime): add reasoning toggle for ollama
This commit is contained in:
parent
8f13fee4a6
commit
a5d7911923
10 changed files with 289 additions and 31 deletions
|
|
@ -50,6 +50,18 @@ Notes:
|
|||
- Setting `max_tool_iterations = 0` falls back to safe default `10`.
|
||||
- If a channel message exceeds this value, the runtime returns: `Agent exceeded maximum tool iterations (<value>)`.
|
||||
|
||||
## `[runtime]`
|
||||
|
||||
| Key | Default | Purpose |
|
||||
|---|---|---|
|
||||
| `reasoning_enabled` | unset (`None`) | Global reasoning/thinking override for providers that support explicit controls |
|
||||
|
||||
Notes:
|
||||
|
||||
- `reasoning_enabled = false` explicitly disables provider-side reasoning for supported providers (currently `ollama`, via request field `think: false`).
|
||||
- `reasoning_enabled = true` explicitly requests reasoning for supported providers (`think: true` on `ollama`).
|
||||
- Unset keeps provider defaults.
|
||||
|
||||
## `[gateway]`
|
||||
|
||||
| Key | Default | Purpose |
|
||||
|
|
|
|||
|
|
@ -67,6 +67,21 @@ credential is not reused for fallback providers.
|
|||
- Cross-region inference profiles supported (e.g., `us.anthropic.claude-*`).
|
||||
- Model IDs use Bedrock format: `anthropic.claude-sonnet-4-6`, `anthropic.claude-opus-4-6-v1`, etc.
|
||||
|
||||
### Ollama Reasoning Toggle
|
||||
|
||||
You can control Ollama reasoning/thinking behavior from `config.toml`:
|
||||
|
||||
```toml
|
||||
[runtime]
|
||||
reasoning_enabled = false
|
||||
```
|
||||
|
||||
Behavior:
|
||||
|
||||
- `false`: sends `think: false` to Ollama `/api/chat` requests.
|
||||
- `true`: sends `think: true`.
|
||||
- Unset: omits `think` and keeps Ollama/model defaults.
|
||||
|
||||
### Kimi Code Notes
|
||||
|
||||
- Provider ID: `kimi-code`
|
||||
|
|
|
|||
|
|
@ -1191,13 +1191,21 @@ pub async fn run(
|
|||
.or(config.default_model.as_deref())
|
||||
.unwrap_or("anthropic/claude-sonnet-4");
|
||||
|
||||
let provider: Box<dyn Provider> = providers::create_routed_provider(
|
||||
let provider_runtime_options = providers::ProviderRuntimeOptions {
|
||||
auth_profile_override: None,
|
||||
zeroclaw_dir: config.config_path.parent().map(std::path::PathBuf::from),
|
||||
secrets_encrypt: config.secrets.encrypt,
|
||||
reasoning_enabled: config.runtime.reasoning_enabled,
|
||||
};
|
||||
|
||||
let provider: Box<dyn Provider> = providers::create_routed_provider_with_options(
|
||||
provider_name,
|
||||
config.api_key.as_deref(),
|
||||
config.api_url.as_deref(),
|
||||
&config.reliability,
|
||||
&config.model_routes,
|
||||
model_name,
|
||||
&provider_runtime_options,
|
||||
)?;
|
||||
|
||||
observer.record_event(&ObserverEvent::AgentStart {
|
||||
|
|
@ -1632,13 +1640,20 @@ pub async fn process_message(config: Config, message: &str) -> Result<String> {
|
|||
.default_model
|
||||
.clone()
|
||||
.unwrap_or_else(|| "anthropic/claude-sonnet-4-20250514".into());
|
||||
let provider: Box<dyn Provider> = providers::create_routed_provider(
|
||||
let provider_runtime_options = providers::ProviderRuntimeOptions {
|
||||
auth_profile_override: None,
|
||||
zeroclaw_dir: config.config_path.parent().map(std::path::PathBuf::from),
|
||||
secrets_encrypt: config.secrets.encrypt,
|
||||
reasoning_enabled: config.runtime.reasoning_enabled,
|
||||
};
|
||||
let provider: Box<dyn Provider> = providers::create_routed_provider_with_options(
|
||||
provider_name,
|
||||
config.api_key.as_deref(),
|
||||
config.api_url.as_deref(),
|
||||
&config.reliability,
|
||||
&config.model_routes,
|
||||
&model_name,
|
||||
&provider_runtime_options,
|
||||
)?;
|
||||
|
||||
let hardware_rag: Option<crate::rag::HardwareRag> = config
|
||||
|
|
|
|||
|
|
@ -1672,6 +1672,7 @@ pub async fn start_channels(config: Config) -> Result<()> {
|
|||
auth_profile_override: None,
|
||||
zeroclaw_dir: config.config_path.parent().map(std::path::PathBuf::from),
|
||||
secrets_encrypt: config.secrets.encrypt,
|
||||
reasoning_enabled: config.runtime.reasoning_enabled,
|
||||
};
|
||||
let provider: Arc<dyn Provider> = Arc::from(providers::create_resilient_provider_with_options(
|
||||
&provider_name,
|
||||
|
|
|
|||
|
|
@ -1607,6 +1607,13 @@ pub struct RuntimeConfig {
|
|||
/// Docker runtime settings (used when `kind = "docker"`).
|
||||
#[serde(default)]
|
||||
pub docker: DockerRuntimeConfig,
|
||||
|
||||
/// Global reasoning override for providers that expose explicit controls.
|
||||
/// - `None`: provider default behavior
|
||||
/// - `Some(true)`: request reasoning/thinking when supported
|
||||
/// - `Some(false)`: disable reasoning/thinking when supported
|
||||
#[serde(default)]
|
||||
pub reasoning_enabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
|
|
@ -1679,6 +1686,7 @@ impl Default for RuntimeConfig {
|
|||
Self {
|
||||
kind: default_runtime_kind(),
|
||||
docker: DockerRuntimeConfig::default(),
|
||||
reasoning_enabled: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2979,6 +2987,18 @@ impl Config {
|
|||
}
|
||||
}
|
||||
|
||||
// Reasoning override: ZEROCLAW_REASONING_ENABLED or REASONING_ENABLED
|
||||
if let Ok(flag) = std::env::var("ZEROCLAW_REASONING_ENABLED")
|
||||
.or_else(|_| std::env::var("REASONING_ENABLED"))
|
||||
{
|
||||
let normalized = flag.trim().to_ascii_lowercase();
|
||||
match normalized.as_str() {
|
||||
"1" | "true" | "yes" | "on" => self.runtime.reasoning_enabled = Some(true),
|
||||
"0" | "false" | "no" | "off" => self.runtime.reasoning_enabled = Some(false),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Web search enabled: ZEROCLAW_WEB_SEARCH_ENABLED or WEB_SEARCH_ENABLED
|
||||
if let Ok(enabled) = std::env::var("ZEROCLAW_WEB_SEARCH_ENABLED")
|
||||
.or_else(|_| std::env::var("WEB_SEARCH_ENABLED"))
|
||||
|
|
@ -3560,6 +3580,19 @@ connect_timeout_secs = 12
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_reasoning_enabled_deserializes() {
|
||||
let raw = r#"
|
||||
default_temperature = 0.7
|
||||
|
||||
[runtime]
|
||||
reasoning_enabled = false
|
||||
"#;
|
||||
|
||||
let parsed: Config = toml::from_str(raw).unwrap();
|
||||
assert_eq!(parsed.runtime.reasoning_enabled, Some(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn agent_config_defaults() {
|
||||
let cfg = AgentConfig::default();
|
||||
|
|
@ -5001,6 +5034,36 @@ default_model = "legacy-model"
|
|||
std::env::remove_var("ZEROCLAW_TEMPERATURE");
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn env_override_reasoning_enabled() {
|
||||
let _env_guard = env_override_lock().await;
|
||||
let mut config = Config::default();
|
||||
assert_eq!(config.runtime.reasoning_enabled, None);
|
||||
|
||||
std::env::set_var("ZEROCLAW_REASONING_ENABLED", "false");
|
||||
config.apply_env_overrides();
|
||||
assert_eq!(config.runtime.reasoning_enabled, Some(false));
|
||||
|
||||
std::env::set_var("ZEROCLAW_REASONING_ENABLED", "true");
|
||||
config.apply_env_overrides();
|
||||
assert_eq!(config.runtime.reasoning_enabled, Some(true));
|
||||
|
||||
std::env::remove_var("ZEROCLAW_REASONING_ENABLED");
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn env_override_reasoning_invalid_value_ignored() {
|
||||
let _env_guard = env_override_lock().await;
|
||||
let mut config = Config::default();
|
||||
config.runtime.reasoning_enabled = Some(false);
|
||||
|
||||
std::env::set_var("ZEROCLAW_REASONING_ENABLED", "maybe");
|
||||
config.apply_env_overrides();
|
||||
assert_eq!(config.runtime.reasoning_enabled, Some(false));
|
||||
|
||||
std::env::remove_var("ZEROCLAW_REASONING_ENABLED");
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn env_override_invalid_port_ignored() {
|
||||
let _env_guard = env_override_lock().await;
|
||||
|
|
|
|||
|
|
@ -313,6 +313,7 @@ pub async fn run_gateway(host: &str, port: u16, config: Config) -> Result<()> {
|
|||
auth_profile_override: None,
|
||||
zeroclaw_dir: config.config_path.parent().map(std::path::PathBuf::from),
|
||||
secrets_encrypt: config.secrets.encrypt,
|
||||
reasoning_enabled: config.runtime.reasoning_enabled,
|
||||
},
|
||||
)?);
|
||||
let model = config
|
||||
|
|
|
|||
|
|
@ -630,6 +630,7 @@ pub struct ProviderRuntimeOptions {
|
|||
pub auth_profile_override: Option<String>,
|
||||
pub zeroclaw_dir: Option<PathBuf>,
|
||||
pub secrets_encrypt: bool,
|
||||
pub reasoning_enabled: Option<bool>,
|
||||
}
|
||||
|
||||
impl Default for ProviderRuntimeOptions {
|
||||
|
|
@ -638,6 +639,7 @@ impl Default for ProviderRuntimeOptions {
|
|||
auth_profile_override: None,
|
||||
zeroclaw_dir: None,
|
||||
secrets_encrypt: true,
|
||||
reasoning_enabled: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -865,16 +867,26 @@ pub fn create_provider_with_options(
|
|||
"openai-codex" | "openai_codex" | "codex" => {
|
||||
Ok(Box::new(openai_codex::OpenAiCodexProvider::new(options)))
|
||||
}
|
||||
_ => create_provider_with_url(name, api_key, None),
|
||||
_ => create_provider_with_url_and_options(name, api_key, None, options),
|
||||
}
|
||||
}
|
||||
|
||||
/// Factory: create the right provider from config with optional custom base URL
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn create_provider_with_url(
|
||||
name: &str,
|
||||
api_key: Option<&str>,
|
||||
api_url: Option<&str>,
|
||||
) -> anyhow::Result<Box<dyn Provider>> {
|
||||
create_provider_with_url_and_options(name, api_key, api_url, &ProviderRuntimeOptions::default())
|
||||
}
|
||||
|
||||
/// Factory: create provider with optional base URL and runtime options.
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn create_provider_with_url_and_options(
|
||||
name: &str,
|
||||
api_key: Option<&str>,
|
||||
api_url: Option<&str>,
|
||||
options: &ProviderRuntimeOptions,
|
||||
) -> anyhow::Result<Box<dyn Provider>> {
|
||||
let qwen_oauth_context = is_qwen_oauth_alias(name).then(|| resolve_qwen_oauth_context(api_key));
|
||||
|
||||
|
|
@ -895,7 +907,11 @@ pub fn create_provider_with_url(
|
|||
"anthropic" => Ok(Box::new(anthropic::AnthropicProvider::new(key))),
|
||||
"openai" => Ok(Box::new(openai::OpenAiProvider::with_base_url(api_url, key))),
|
||||
// Ollama uses api_url for custom base URL (e.g. remote Ollama instance)
|
||||
"ollama" => Ok(Box::new(ollama::OllamaProvider::new(api_url, key))),
|
||||
"ollama" => Ok(Box::new(ollama::OllamaProvider::new_with_reasoning(
|
||||
api_url,
|
||||
key,
|
||||
options.reasoning_enabled,
|
||||
))),
|
||||
"gemini" | "google" | "google-gemini" => {
|
||||
Ok(Box::new(gemini::GeminiProvider::new(key)))
|
||||
}
|
||||
|
|
@ -1109,7 +1125,7 @@ pub fn create_resilient_provider_with_options(
|
|||
"openai-codex" | "openai_codex" | "codex" => {
|
||||
create_provider_with_options(primary_name, api_key, options)?
|
||||
}
|
||||
_ => create_provider_with_url(primary_name, api_key, api_url)?,
|
||||
_ => create_provider_with_url_and_options(primary_name, api_key, api_url, options)?,
|
||||
};
|
||||
providers.push((primary_name.to_string(), primary_provider));
|
||||
|
||||
|
|
@ -1159,9 +1175,36 @@ pub fn create_routed_provider(
|
|||
reliability: &crate::config::ReliabilityConfig,
|
||||
model_routes: &[crate::config::ModelRouteConfig],
|
||||
default_model: &str,
|
||||
) -> anyhow::Result<Box<dyn Provider>> {
|
||||
create_routed_provider_with_options(
|
||||
primary_name,
|
||||
api_key,
|
||||
api_url,
|
||||
reliability,
|
||||
model_routes,
|
||||
default_model,
|
||||
&ProviderRuntimeOptions::default(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a routed provider using explicit runtime options.
|
||||
pub fn create_routed_provider_with_options(
|
||||
primary_name: &str,
|
||||
api_key: Option<&str>,
|
||||
api_url: Option<&str>,
|
||||
reliability: &crate::config::ReliabilityConfig,
|
||||
model_routes: &[crate::config::ModelRouteConfig],
|
||||
default_model: &str,
|
||||
options: &ProviderRuntimeOptions,
|
||||
) -> anyhow::Result<Box<dyn Provider>> {
|
||||
if model_routes.is_empty() {
|
||||
return create_resilient_provider(primary_name, api_key, api_url, reliability);
|
||||
return create_resilient_provider_with_options(
|
||||
primary_name,
|
||||
api_key,
|
||||
api_url,
|
||||
reliability,
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
// Collect unique provider names needed
|
||||
|
|
@ -1187,7 +1230,7 @@ pub fn create_routed_provider(
|
|||
let key = routed_credential.or(api_key);
|
||||
// Only use api_url for the primary provider
|
||||
let url = if name == primary_name { api_url } else { None };
|
||||
match create_resilient_provider(name, key, url, reliability) {
|
||||
match create_resilient_provider_with_options(name, key, url, reliability, options) {
|
||||
Ok(provider) => providers.push((name.clone(), provider)),
|
||||
Err(e) => {
|
||||
if name == primary_name {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use std::collections::HashMap;
|
|||
pub struct OllamaProvider {
|
||||
base_url: String,
|
||||
api_key: Option<String>,
|
||||
reasoning_enabled: Option<bool>,
|
||||
}
|
||||
|
||||
// ─── Request Structures ───────────────────────────────────────────────────────
|
||||
|
|
@ -18,6 +19,8 @@ struct ChatRequest {
|
|||
stream: bool,
|
||||
options: Options,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
think: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
tools: Option<Vec<serde_json::Value>>,
|
||||
}
|
||||
|
||||
|
|
@ -85,6 +88,14 @@ struct OllamaFunction {
|
|||
|
||||
impl OllamaProvider {
|
||||
pub fn new(base_url: Option<&str>, api_key: Option<&str>) -> Self {
|
||||
Self::new_with_reasoning(base_url, api_key, None)
|
||||
}
|
||||
|
||||
pub fn new_with_reasoning(
|
||||
base_url: Option<&str>,
|
||||
api_key: Option<&str>,
|
||||
reasoning_enabled: Option<bool>,
|
||||
) -> Self {
|
||||
let api_key = api_key.and_then(|value| {
|
||||
let trimmed = value.trim();
|
||||
(!trimmed.is_empty()).then(|| trimmed.to_string())
|
||||
|
|
@ -96,6 +107,7 @@ impl OllamaProvider {
|
|||
.trim_end_matches('/')
|
||||
.to_string(),
|
||||
api_key,
|
||||
reasoning_enabled,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -137,6 +149,23 @@ impl OllamaProvider {
|
|||
serde_json::from_str(arguments).unwrap_or_else(|_| serde_json::json!({}))
|
||||
}
|
||||
|
||||
fn build_chat_request(
|
||||
&self,
|
||||
messages: Vec<Message>,
|
||||
model: &str,
|
||||
temperature: f64,
|
||||
tools: Option<&[serde_json::Value]>,
|
||||
) -> ChatRequest {
|
||||
ChatRequest {
|
||||
model: model.to_string(),
|
||||
messages,
|
||||
stream: false,
|
||||
options: Options { temperature },
|
||||
think: self.reasoning_enabled,
|
||||
tools: tools.map(|t| t.to_vec()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert internal chat history format to Ollama's native tool-call message schema.
|
||||
///
|
||||
/// `run_tool_call_loop` stores native assistant/tool entries as JSON strings in
|
||||
|
|
@ -235,22 +264,17 @@ impl OllamaProvider {
|
|||
should_auth: bool,
|
||||
tools: Option<&[serde_json::Value]>,
|
||||
) -> anyhow::Result<ApiChatResponse> {
|
||||
let request = ChatRequest {
|
||||
model: model.to_string(),
|
||||
messages,
|
||||
stream: false,
|
||||
options: Options { temperature },
|
||||
tools: tools.map(|t| t.to_vec()),
|
||||
};
|
||||
let request = self.build_chat_request(messages, model, temperature, tools);
|
||||
|
||||
let url = format!("{}/api/chat", self.base_url);
|
||||
|
||||
tracing::debug!(
|
||||
"Ollama request: url={} model={} message_count={} temperature={} tool_count={}",
|
||||
"Ollama request: url={} model={} message_count={} temperature={} think={:?} tool_count={}",
|
||||
url,
|
||||
model,
|
||||
request.messages.len(),
|
||||
temperature,
|
||||
request.think,
|
||||
request.tools.as_ref().map_or(0, |t| t.len()),
|
||||
);
|
||||
|
||||
|
|
@ -645,6 +669,44 @@ mod tests {
|
|||
assert!(!should_auth);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_omits_think_when_reasoning_not_configured() {
|
||||
let provider = OllamaProvider::new(None, None);
|
||||
let request = provider.build_chat_request(
|
||||
vec![Message {
|
||||
role: "user".to_string(),
|
||||
content: Some("hello".to_string()),
|
||||
tool_calls: None,
|
||||
tool_name: None,
|
||||
}],
|
||||
"llama3",
|
||||
0.7,
|
||||
None,
|
||||
);
|
||||
|
||||
let json = serde_json::to_value(request).unwrap();
|
||||
assert!(json.get("think").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_includes_think_when_reasoning_configured() {
|
||||
let provider = OllamaProvider::new_with_reasoning(None, None, Some(false));
|
||||
let request = provider.build_chat_request(
|
||||
vec![Message {
|
||||
role: "user".to_string(),
|
||||
content: Some("hello".to_string()),
|
||||
tool_calls: None,
|
||||
tool_name: None,
|
||||
}],
|
||||
"llama3",
|
||||
0.7,
|
||||
None,
|
||||
);
|
||||
|
||||
let json = serde_json::to_value(request).unwrap();
|
||||
assert_eq!(json.get("think"), Some(&serde_json::json!(false)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_deserializes() {
|
||||
let json = r#"{"message":{"role":"assistant","content":"Hello from Ollama!"}}"#;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ pub struct DelegateTool {
|
|||
security: Arc<SecurityPolicy>,
|
||||
/// Global credential fallback (from config.api_key)
|
||||
fallback_credential: Option<String>,
|
||||
/// Provider runtime options inherited from root config.
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions,
|
||||
/// Depth at which this tool instance lives in the delegation chain.
|
||||
depth: u32,
|
||||
}
|
||||
|
|
@ -30,11 +32,26 @@ impl DelegateTool {
|
|||
agents: HashMap<String, DelegateAgentConfig>,
|
||||
fallback_credential: Option<String>,
|
||||
security: Arc<SecurityPolicy>,
|
||||
) -> Self {
|
||||
Self::new_with_options(
|
||||
agents,
|
||||
fallback_credential,
|
||||
security,
|
||||
providers::ProviderRuntimeOptions::default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_with_options(
|
||||
agents: HashMap<String, DelegateAgentConfig>,
|
||||
fallback_credential: Option<String>,
|
||||
security: Arc<SecurityPolicy>,
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions,
|
||||
) -> Self {
|
||||
Self {
|
||||
agents: Arc::new(agents),
|
||||
security,
|
||||
fallback_credential,
|
||||
provider_runtime_options,
|
||||
depth: 0,
|
||||
}
|
||||
}
|
||||
|
|
@ -47,11 +64,28 @@ impl DelegateTool {
|
|||
fallback_credential: Option<String>,
|
||||
security: Arc<SecurityPolicy>,
|
||||
depth: u32,
|
||||
) -> Self {
|
||||
Self::with_depth_and_options(
|
||||
agents,
|
||||
fallback_credential,
|
||||
security,
|
||||
depth,
|
||||
providers::ProviderRuntimeOptions::default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn with_depth_and_options(
|
||||
agents: HashMap<String, DelegateAgentConfig>,
|
||||
fallback_credential: Option<String>,
|
||||
security: Arc<SecurityPolicy>,
|
||||
depth: u32,
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions,
|
||||
) -> Self {
|
||||
Self {
|
||||
agents: Arc::new(agents),
|
||||
security,
|
||||
fallback_credential,
|
||||
provider_runtime_options,
|
||||
depth,
|
||||
}
|
||||
}
|
||||
|
|
@ -190,20 +224,23 @@ impl Tool for DelegateTool {
|
|||
#[allow(clippy::option_as_ref_deref)]
|
||||
let provider_credential = provider_credential_owned.as_ref().map(String::as_str);
|
||||
|
||||
let provider: Box<dyn Provider> =
|
||||
match providers::create_provider(&agent_config.provider, provider_credential) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
return Ok(ToolResult {
|
||||
success: false,
|
||||
output: String::new(),
|
||||
error: Some(format!(
|
||||
"Failed to create provider '{}' for agent '{agent_name}': {e}",
|
||||
agent_config.provider
|
||||
)),
|
||||
});
|
||||
}
|
||||
};
|
||||
let provider: Box<dyn Provider> = match providers::create_provider_with_options(
|
||||
&agent_config.provider,
|
||||
provider_credential,
|
||||
&self.provider_runtime_options,
|
||||
) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
return Ok(ToolResult {
|
||||
success: false,
|
||||
output: String::new(),
|
||||
error: Some(format!(
|
||||
"Failed to create provider '{}' for agent '{agent_name}': {e}",
|
||||
agent_config.provider
|
||||
)),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Build the message
|
||||
let full_prompt = if context.is_empty() {
|
||||
|
|
|
|||
|
|
@ -227,10 +227,19 @@ pub fn all_tools_with_runtime(
|
|||
let trimmed_value = value.trim();
|
||||
(!trimmed_value.is_empty()).then(|| trimmed_value.to_owned())
|
||||
});
|
||||
tools.push(Box::new(DelegateTool::new(
|
||||
tools.push(Box::new(DelegateTool::new_with_options(
|
||||
delegate_agents,
|
||||
delegate_fallback_credential,
|
||||
security.clone(),
|
||||
crate::providers::ProviderRuntimeOptions {
|
||||
auth_profile_override: None,
|
||||
zeroclaw_dir: root_config
|
||||
.config_path
|
||||
.parent()
|
||||
.map(std::path::PathBuf::from),
|
||||
secrets_encrypt: root_config.secrets.encrypt,
|
||||
reasoning_enabled: root_config.runtime.reasoning_enabled,
|
||||
},
|
||||
)));
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue