🐛 Fix Ollama auth + config format
Fixes:
1. Added to (config.rs)
2. Rewrote with dual-mode logic:
- OpenAI-compatible mode (api_key set OR non-localhost URL):
uses /chat/completions + Bearer auth
- Native mode: uses /api/chat (no auth)
3. Updated in agent_loop.rs to pass api_key
4. Updated to pass api_key
5. Added in main.rs — parses
flat format AND canonical format
6. Added dependency to root Cargo.toml
The
o
|
- - - - - - .---. - - - - - -
- - - - - - - - - [ ^ ^ ] - - - - - - - - -
- - - - - - - | ___ | - - - - - - -
`-----'
____ __ __ ___ _ _ ____ ___ ___ _____
/ ___|\ \ / / / _ \ | \ | || _ \ / _ \ / _ \|_ _|
| | _ \ V / / /_\ \| \| || | | | | | | | | | | |
| |_| | | | | _ || |\ || |_| | |_| | |_| | | |
\____/ |_| |_| |_||_| \_||____/ \___/ \___/ |_|
o - - - - - - - knowledge. delivered. - - - - - - - o
╔══════════════════════════════════════════════╗
║ ज्ञानदूत GyanDoot — Setup Wizard ║
╚══════════════════════════════════════════════╝
Configure your AI provider in 4 simple steps
This wizard will help you configure GyanDoot to connect
to your preferred LLM provider. You can re-run this anytime.
Select a provider:
────────────────────────────────────────────────────────────
1. Alibaba (Qwen) (alibaba)
2. Anthropic (anthropic)
3. Azure OpenAI (azure)
4. Amazon Bedrock (bedrock)
5. DeepSeek (deepseek)
6. Google Gemini (gemini)
7. Groq (groq)
8. HuggingFace (huggingface)
9. Kilocode (kilocode)
10. MiniMax (minimax)
11. Novita AI (novita)
12. Ollama (Local or Cloud) (ollama)
13. OpenAI (openai)
14. OpenRouter (openrouter)
15. StepFun (stepfun)
16. Together AI (together)
17. xAI / Grok (xai)
18. Xiaomi (MiLM) (xiaomi)
19. ZAI (01.AI / Yi) (zai)
20. Custom / Other
Enter number (1-20): wizard writes (singular) but
the config loader only parsed (plural). Now both
formats work. Ollama cloud endpoints (ollama.com/v1) get proper
Bearer auth with the API key from the setup wizard.
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -663,6 +663,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -82,3 +82,4 @@ tracing.workspace = true
|
|||||||
tracing-subscriber.workspace = true
|
tracing-subscriber.workspace = true
|
||||||
serde_yaml.workspace = true
|
serde_yaml.workspace = true
|
||||||
dirs.workspace = true
|
dirs.workspace = true
|
||||||
|
toml.workspace = true
|
||||||
@@ -551,6 +551,7 @@ fn create_provider(config: &AgentConfig) -> Box<dyn Provider + Send + Sync> {
|
|||||||
Box::new(gyandoot_providers::ollama::OllamaProvider::new(
|
Box::new(gyandoot_providers::ollama::OllamaProvider::new(
|
||||||
cfg.base_url,
|
cfg.base_url,
|
||||||
cfg.model,
|
cfg.model,
|
||||||
|
cfg.api_key.clone(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
|
|||||||
@@ -404,6 +404,8 @@ impl Default for AnthropicConfig {
|
|||||||
pub struct OllamaConfig {
|
pub struct OllamaConfig {
|
||||||
pub base_url: String,
|
pub base_url: String,
|
||||||
pub model: String,
|
pub model: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub api_key: Option<String>,
|
||||||
pub max_retries: u32,
|
pub max_retries: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,6 +418,7 @@ impl OllamaConfig {
|
|||||||
Self {
|
Self {
|
||||||
base_url: table.get("base_url").and_then(|v| v.as_str()).unwrap_or("http://localhost:11434").to_string(),
|
base_url: table.get("base_url").and_then(|v| v.as_str()).unwrap_or("http://localhost:11434").to_string(),
|
||||||
model: table.get("model").and_then(|v| v.as_str()).unwrap_or("llama3").to_string(),
|
model: table.get("model").and_then(|v| v.as_str()).unwrap_or("llama3").to_string(),
|
||||||
|
api_key: table.get("api_key").and_then(|v| v.as_str()).map(|s| s.to_string()),
|
||||||
max_retries: table.get("max_retries").and_then(|v| v.as_integer()).unwrap_or(2) as u32,
|
max_retries: table.get("max_retries").and_then(|v| v.as_integer()).unwrap_or(2) as u32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -426,6 +429,7 @@ impl Default for OllamaConfig {
|
|||||||
Self {
|
Self {
|
||||||
base_url: "http://localhost:11434".to_string(),
|
base_url: "http://localhost:11434".to_string(),
|
||||||
model: "llama3".to_string(),
|
model: "llama3".to_string(),
|
||||||
|
api_key: None,
|
||||||
max_retries: 2,
|
max_retries: 2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ impl ProviderFactory {
|
|||||||
Ok(Box::new(ollama::OllamaProvider::new(
|
Ok(Box::new(ollama::OllamaProvider::new(
|
||||||
cfg.base_url.clone(),
|
cfg.base_url.clone(),
|
||||||
cfg.model.clone(),
|
cfg.model.clone(),
|
||||||
|
cfg.api_key.clone(),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
ProviderKind::Gemini => {
|
ProviderKind::Gemini => {
|
||||||
@@ -286,6 +287,7 @@ impl ProviderFactory {
|
|||||||
Some(gyandoot_core::OllamaConfig {
|
Some(gyandoot_core::OllamaConfig {
|
||||||
base_url: default_base_url(&kind),
|
base_url: default_base_url(&kind),
|
||||||
model: model.to_string(),
|
model: model.to_string(),
|
||||||
|
api_key: None,
|
||||||
max_retries: 2,
|
max_retries: 2,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
//! Ollama API provider implementation.
|
//! Ollama API provider implementation.
|
||||||
|
//!
|
||||||
|
//! Supports both native Ollama `/api/chat` and OpenAI-compatible
|
||||||
|
//! `/chat/completions` endpoints. Auto-detects which to use based on
|
||||||
|
//! the presence of an `api_key` and whether the URL looks like localhost.
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use gyandoot_core::{ConversationContext, ProviderResult};
|
use gyandoot_core::{ConversationContext, ProviderResult};
|
||||||
@@ -7,79 +11,161 @@ use tracing::debug;
|
|||||||
|
|
||||||
use crate::{Provider, ProviderResponse};
|
use crate::{Provider, ProviderResponse};
|
||||||
|
|
||||||
/// Provider for local Ollama models.
|
/// Provider for Ollama models (native + OpenAI-compatible).
|
||||||
pub struct OllamaProvider {
|
pub struct OllamaProvider {
|
||||||
base_url: String,
|
base_url: String,
|
||||||
model: String,
|
model: String,
|
||||||
|
api_key: Option<String>,
|
||||||
client: Client,
|
client: Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OllamaProvider {
|
impl OllamaProvider {
|
||||||
/// Create a new Ollama provider.
|
/// Create a new Ollama provider.
|
||||||
pub fn new(base_url: String, model: String) -> Self {
|
///
|
||||||
|
/// - `base_url`: The Ollama API base URL.
|
||||||
|
/// For native Ollama: `http://localhost:11434`
|
||||||
|
/// For cloud/OpenAI-compatible: `http://ollama.com/v1`
|
||||||
|
/// - `model`: Model name (e.g. `"llama3"`, `"qwen3.5:cloud"`)
|
||||||
|
/// - `api_key`: Optional API key. Required for cloud/OpenAI-compatible endpoints.
|
||||||
|
pub fn new(base_url: String, model: String, api_key: Option<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
base_url,
|
base_url: base_url.trim_end_matches('/').to_string(),
|
||||||
model,
|
model,
|
||||||
|
api_key,
|
||||||
client: Client::new(),
|
client: Client::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if this is an OpenAI-compatible endpoint (not native Ollama).
|
||||||
|
/// Heuristic: if api_key is set or URL is not localhost/127.0.0.1, treat as OpenAI-compatible.
|
||||||
|
fn is_openai_compatible(&self) -> bool {
|
||||||
|
if self.api_key.is_some() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let lower = self.base_url.to_lowercase();
|
||||||
|
!lower.contains("localhost") && !lower.contains("127.0.0.1") && !lower.contains("0.0.0.0")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Provider for OllamaProvider {
|
impl Provider for OllamaProvider {
|
||||||
async fn chat(&self, context: ConversationContext) -> ProviderResult<ProviderResponse> {
|
async fn chat(&self, context: ConversationContext) -> ProviderResult<ProviderResponse> {
|
||||||
debug!("Ollama chat request (model: {}, url: {})", self.model, self.base_url);
|
debug!(
|
||||||
|
"Ollama chat request (model: {}, url: {}, openai_compat: {})",
|
||||||
|
self.model,
|
||||||
|
self.base_url,
|
||||||
|
self.is_openai_compatible()
|
||||||
|
);
|
||||||
|
|
||||||
let messages: Vec<serde_json::Value> = context
|
if self.is_openai_compatible() {
|
||||||
.messages
|
// --- OpenAI-compatible mode (/chat/completions) ---
|
||||||
.iter()
|
let messages: Vec<serde_json::Value> = context
|
||||||
.map(|m| {
|
.messages
|
||||||
serde_json::json!({
|
.iter()
|
||||||
"role": m.role.to_string(),
|
.map(|m| {
|
||||||
"content": m.content,
|
serde_json::json!({
|
||||||
|
"role": m.role.to_string(),
|
||||||
|
"content": m.content,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
.collect();
|
||||||
.collect();
|
|
||||||
|
|
||||||
let body = serde_json::json!({
|
let body = serde_json::json!({
|
||||||
"model": self.model,
|
"model": self.model,
|
||||||
"messages": messages,
|
"messages": messages,
|
||||||
"stream": false,
|
"max_tokens": context.max_tokens,
|
||||||
"options": {
|
"stream": false,
|
||||||
"num_predict": context.max_tokens,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let url = format!("{}/api/chat", self.base_url.trim_end_matches('/'));
|
|
||||||
let response = self
|
|
||||||
.client
|
|
||||||
.post(&url)
|
|
||||||
.json(&body)
|
|
||||||
.send()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if !response.status().is_success() {
|
|
||||||
return Err(gyandoot_core::ProviderError::Api {
|
|
||||||
status: response.status().as_u16(),
|
|
||||||
message: format!(
|
|
||||||
"Ollama API error: {}",
|
|
||||||
response.status()
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let url = format!("{}/chat/completions", self.base_url);
|
||||||
|
let mut request = self.client.post(&url).json(&body);
|
||||||
|
|
||||||
|
// Add bearer auth if api_key is set
|
||||||
|
if let Some(ref key) = self.api_key {
|
||||||
|
request = request.header("Authorization", format!("Bearer {}", key));
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = request.send().await.map_err(|e| {
|
||||||
|
gyandoot_core::ProviderError::Request(e)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
let status = response.status().as_u16();
|
||||||
|
let error_text = response.text().await.unwrap_or_default();
|
||||||
|
return Err(gyandoot_core::ProviderError::from_response(status, &error_text));
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: serde_json::Value = response.json().await.map_err(|e| {
|
||||||
|
gyandoot_core::ProviderError::Request(e)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let content = data["choices"][0]["message"]["content"]
|
||||||
|
.as_str()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string();
|
||||||
|
let finish_reason = data["choices"][0]["finish_reason"]
|
||||||
|
.as_str()
|
||||||
|
.unwrap_or("stop")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
Ok(ProviderResponse {
|
||||||
|
content,
|
||||||
|
tool_calls: None,
|
||||||
|
finish_reason,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// --- Native Ollama mode (/api/chat) ---
|
||||||
|
let messages: Vec<serde_json::Value> = context
|
||||||
|
.messages
|
||||||
|
.iter()
|
||||||
|
.map(|m| {
|
||||||
|
serde_json::json!({
|
||||||
|
"role": m.role.to_string(),
|
||||||
|
"content": m.content,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let body = serde_json::json!({
|
||||||
|
"model": self.model,
|
||||||
|
"messages": messages,
|
||||||
|
"stream": false,
|
||||||
|
"options": {
|
||||||
|
"num_predict": context.max_tokens,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let url = format!("{}/api/chat", self.base_url);
|
||||||
|
let response = self
|
||||||
|
.client
|
||||||
|
.post(&url)
|
||||||
|
.json(&body)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| gyandoot_core::ProviderError::Request(e))?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return Err(gyandoot_core::ProviderError::Api {
|
||||||
|
status: response.status().as_u16(),
|
||||||
|
message: format!("Ollama API error: {}", response.status()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: serde_json::Value = response.json().await.map_err(|e| {
|
||||||
|
gyandoot_core::ProviderError::Request(e)
|
||||||
|
})?;
|
||||||
|
let content = data["message"]["content"]
|
||||||
|
.as_str()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string();
|
||||||
|
let finish_reason = data["done_reason"].as_str().unwrap_or("stop").to_string();
|
||||||
|
|
||||||
|
Ok(ProviderResponse {
|
||||||
|
content,
|
||||||
|
tool_calls: None,
|
||||||
|
finish_reason,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let data: serde_json::Value = response.json().await?;
|
|
||||||
let content = data["message"]["content"]
|
|
||||||
.as_str()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_string();
|
|
||||||
let finish_reason = data["done_reason"].as_str().unwrap_or("stop").to_string();
|
|
||||||
|
|
||||||
Ok(ProviderResponse {
|
|
||||||
content,
|
|
||||||
tool_calls: None,
|
|
||||||
finish_reason,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn chat_stream(
|
async fn chat_stream(
|
||||||
@@ -100,21 +186,55 @@ impl Provider for OllamaProvider {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use gyandoot_core::Message;
|
use gyandoot_core::{Message, MessageRole};
|
||||||
|
|
||||||
|
fn test_messages() -> ConversationContext {
|
||||||
|
ConversationContext {
|
||||||
|
messages: vec![Message::new(MessageRole::User, "hi")],
|
||||||
|
system_prompt: String::new(),
|
||||||
|
tools: vec![],
|
||||||
|
max_tokens: 100,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_openai_compatible_with_api_key() {
|
||||||
|
let provider = OllamaProvider::new(
|
||||||
|
"http://localhost:11434".to_string(),
|
||||||
|
"llama3".to_string(),
|
||||||
|
Some("test-key".to_string()),
|
||||||
|
);
|
||||||
|
assert!(provider.is_openai_compatible());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_openai_compatible_with_remote_url() {
|
||||||
|
let provider = OllamaProvider::new(
|
||||||
|
"http://ollama.com/v1".to_string(),
|
||||||
|
"qwen3.5".to_string(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
assert!(provider.is_openai_compatible());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_native_ollama() {
|
||||||
|
let provider = OllamaProvider::new(
|
||||||
|
"http://localhost:11434".to_string(),
|
||||||
|
"llama3".to_string(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
assert!(!provider.is_openai_compatible());
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_ollama_connection_refused() {
|
async fn test_ollama_connection_refused() {
|
||||||
let provider = OllamaProvider::new(
|
let provider = OllamaProvider::new(
|
||||||
"http://127.0.0.1:1".to_string(),
|
"http://127.0.0.1:1".to_string(),
|
||||||
"llama3".to_string(),
|
"llama3".to_string(),
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
let context = ConversationContext {
|
let result = provider.chat(test_messages()).await;
|
||||||
messages: vec![Message::new(MessageRole::User, "hi")],
|
assert!(result.is_err());
|
||||||
system_prompt: String::new(),
|
|
||||||
tools: vec![],
|
|
||||||
max_tokens: 100,
|
|
||||||
};
|
|
||||||
let result = provider.chat(context).await;
|
|
||||||
assert!(result.is_err()); // Connection refused expected
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
88
src/main.rs
88
src/main.rs
@@ -207,11 +207,8 @@ fn load_agent_config() -> gyandoot_core::config::AgentConfig {
|
|||||||
if let Some(home) = dirs::home_dir() {
|
if let Some(home) = dirs::home_dir() {
|
||||||
let explicit_path = home.join(".config").join("gyandoot").join("config.toml");
|
let explicit_path = home.join(".config").join("gyandoot").join("config.toml");
|
||||||
if explicit_path.exists() {
|
if explicit_path.exists() {
|
||||||
match gyandoot_core::config::ConfigLoader::from_file(&explicit_path) {
|
match load_and_convert_config(&explicit_path) {
|
||||||
Ok(gyandoot_cfg) => {
|
Ok(cfg) => return cfg,
|
||||||
tracing::info!("Loaded config from {}", explicit_path.display());
|
|
||||||
return gyandoot_core::config::AgentConfig::from(gyandoot_cfg);
|
|
||||||
}
|
|
||||||
Err(e) => tracing::warn!("Failed to parse {}: {}", explicit_path.display(), e),
|
Err(e) => tracing::warn!("Failed to parse {}: {}", explicit_path.display(), e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,11 +218,8 @@ fn load_agent_config() -> gyandoot_core::config::AgentConfig {
|
|||||||
if let Some(cfg_dir) = dirs::config_dir() {
|
if let Some(cfg_dir) = dirs::config_dir() {
|
||||||
let xdg_path = cfg_dir.join("gyandoot").join("config.toml");
|
let xdg_path = cfg_dir.join("gyandoot").join("config.toml");
|
||||||
if xdg_path.exists() {
|
if xdg_path.exists() {
|
||||||
match gyandoot_core::config::ConfigLoader::from_file(&xdg_path) {
|
match load_and_convert_config(&xdg_path) {
|
||||||
Ok(gyandoot_cfg) => {
|
Ok(cfg) => return cfg,
|
||||||
tracing::info!("Loaded config from {}", xdg_path.display());
|
|
||||||
return gyandoot_core::config::AgentConfig::from(gyandoot_cfg);
|
|
||||||
}
|
|
||||||
Err(e) => tracing::warn!("Failed to parse {}: {}", xdg_path.display(), e),
|
Err(e) => tracing::warn!("Failed to parse {}: {}", xdg_path.display(), e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,11 +244,83 @@ fn load_agent_config() -> gyandoot_core::config::AgentConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Fallback to hardcoded defaults
|
// 4. Fallback to hardcoded defaults
|
||||||
tracing::warn!("No config file found; using default configuration (API keys will be empty)");
|
tracing::warn!("No config file found; using default configuration (API keys will be empty)");
|
||||||
gyandoot_core::config::AgentConfig::default()
|
gyandoot_core::config::AgentConfig::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Load a TOML config file, converting `[provider]` (singular, flat format
|
||||||
|
/// written by `gyandoot setup`) to the canonical `[providers]` format.
|
||||||
|
fn load_and_convert_config(path: &std::path::Path) -> Result<gyandoot_core::config::AgentConfig, Box<dyn std::error::Error>> {
|
||||||
|
let content = std::fs::read_to_string(path)?;
|
||||||
|
|
||||||
|
// Try parsing as canonical GyandootConfig first
|
||||||
|
if let Ok(gyandoot_cfg) = toml::from_str::<gyandoot_core::config::GyandootConfig>(&content) {
|
||||||
|
tracing::info!("Loaded config from {}", path.display());
|
||||||
|
return Ok(gyandoot_core::config::AgentConfig::from(gyandoot_cfg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: parse flat `[provider]` format and convert to canonical
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct FlatConfig {
|
||||||
|
provider: FlatProvider,
|
||||||
|
agent: Option<FlatAgent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct FlatProvider {
|
||||||
|
kind: String,
|
||||||
|
base_url: String,
|
||||||
|
api_key: Option<String>,
|
||||||
|
model: String,
|
||||||
|
temperature: Option<f64>,
|
||||||
|
max_tokens: Option<u32>,
|
||||||
|
#[serde(default)]
|
||||||
|
merge_system: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
no_tools: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct FlatAgent {
|
||||||
|
name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let flat: FlatConfig = toml::from_str(&content)?;
|
||||||
|
tracing::info!("Loaded config from {} (converted from flat [provider] format)", path.display());
|
||||||
|
|
||||||
|
// Build canonical GyandootConfig from flat data
|
||||||
|
let provider_name = flat.provider.kind.to_lowercase();
|
||||||
|
let mut providers_map = std::collections::HashMap::new();
|
||||||
|
let mut provider_table = toml::map::Map::new();
|
||||||
|
provider_table.insert("model".to_string(), toml::Value::String(flat.provider.model.clone()));
|
||||||
|
provider_table.insert("base_url".to_string(), toml::Value::String(flat.provider.base_url));
|
||||||
|
if let Some(key) = &flat.provider.api_key {
|
||||||
|
provider_table.insert("api_key".to_string(), toml::Value::String(key.clone()));
|
||||||
|
}
|
||||||
|
providers_map.insert(provider_name.clone(), toml::Value::Table(provider_table));
|
||||||
|
|
||||||
|
let gyandoot_cfg = gyandoot_core::config::GyandootConfig {
|
||||||
|
agent: gyandoot_core::config::AgentSection {
|
||||||
|
name: flat.agent.and_then(|a| a.name).unwrap_or_else(|| "gyandoot".to_string()),
|
||||||
|
model: flat.provider.model.clone(),
|
||||||
|
max_tokens: flat.provider.max_tokens.unwrap_or(4096) as usize,
|
||||||
|
temperature: flat.provider.temperature.unwrap_or(0.7),
|
||||||
|
system_prompt: String::new(),
|
||||||
|
},
|
||||||
|
providers: gyandoot_core::config::ProvidersSection {
|
||||||
|
default: provider_name,
|
||||||
|
providers: providers_map,
|
||||||
|
},
|
||||||
|
platform: gyandoot_core::config::PlatformSection::default(),
|
||||||
|
memory: gyandoot_core::config::MemorySection::default(),
|
||||||
|
gateway: gyandoot_core::config::GatewaySection::default(),
|
||||||
|
browser: gyandoot_core::config::BrowserSection::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(gyandoot_core::config::AgentConfig::from(gyandoot_cfg))
|
||||||
|
}
|
||||||
|
|
||||||
/// Start the gateway server for multi-platform sessions
|
/// Start the gateway server for multi-platform sessions
|
||||||
async fn cmd_serve(profile_name: &str, host: &str, port: u16) -> anyhow::Result<()> {
|
async fn cmd_serve(profile_name: &str, host: &str, port: u16) -> anyhow::Result<()> {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
|
|||||||
Reference in New Issue
Block a user