feat: add Fireworks AI provider for chat agent

Routes chat-completions through a managed OpenAI-compatible inference
endpoint as an alternative to local Ollama, useful when the agent needs
fast multi-hop reasoning that local hardware can't sustain.

- backend: rename `call_ollama_chat_messages` → `call_chat_messages`,
  dispatch by provider; add `call_fireworks` branch (Bearer auth,
  `response_format: json_object` mapped from internal `format="json"`)
  and `list_fireworks_models` Tauri command
- settings: extend `AiProvider` enum + `AiSettings.fireworks_api_key`
  (serde-default for legacy config compat); Fireworks base URL hardcoded
- UI: provider selector in both popover and AppSettingsSheet (only
  ollama+fireworks shown; legacy openai/anthropic kept for serde-compat
  but normalized to ollama in UI); password input + dynamic model list
  for Fireworks; switching provider clears stale model selection
- 4 unit tests: serde round-trip, legacy settings deserialization,
  Fireworks chat-completions parsing, models-list parsing
This commit is contained in:
2026-05-06 23:04:10 +03:00
parent 532ebf3b44
commit 96a54edcd0
10 changed files with 524 additions and 65 deletions

View File

@@ -1,4 +1,4 @@
use crate::commands::ai::{build_overview_context, call_ollama_chat_messages};
use crate::commands::ai::{build_overview_context, call_chat_messages};
use crate::commands::chat_tools::{
find_queries_tool, get_columns_tool, list_databases_tool, list_tables_tool, save_query_tool,
switch_database_tool,
@@ -555,7 +555,7 @@ pub async fn chat_send(
let history = build_history(&working, &overview_text, &memory_text);
let raw =
call_ollama_chat_messages(&app, &state, history, Some("json".to_string())).await?;
call_chat_messages(&app, &state, history, Some("json".to_string())).await?;
let trimmed = raw.trim();
let action = match parse_agent_action(trimmed) {
@@ -1039,7 +1039,7 @@ async fn force_final_synthesis(
content: convo,
},
];
match call_ollama_chat_messages(app, state, llm_messages, None).await {
match call_chat_messages(app, state, llm_messages, None).await {
Ok(s) => {
let cleaned = clean_summary(&s);
if cleaned.trim().is_empty() {
@@ -1148,7 +1148,7 @@ pub async fn chat_compact(
content: convo,
},
];
let summary = call_ollama_chat_messages(&app, &state, llm_messages, None)
let summary = call_chat_messages(&app, &state, llm_messages, None)
.await
.map_err(|e| TuskError::Ai(format!("Compact failed: {}", e)))?;