//! [`for_provider`] — 按 provider 配置选 [`CtxBuilder`] 实现的唯一入口。
//!
//! 添加新模型的 ctx 管理策略 **只需两步**:
//!
//! 1. 新建文件 `crates/atomcode-core/src/ctx/<name>.rs`,定义
//! `pub struct XxxCtx` 并 `impl CtxBuilder for XxxCtx`
//! 2. 在 [`for_provider`] 里插一条 `if` 分支,匹配
//! `provider.provider_type` 或 `provider.model` 前缀。
//!
//! 未命中任何规则 → [`DefaultCtx`],保留 atomcode 当前的上下文行为
//! 不变。
//!
//! 规则表是 **按顺序匹配**:靠上的规则优先生效。当一个 provider
//! 同时匹配多个规则(例如 `provider_type == "ollama"` 且
//! `model.starts_with("claude-")`,虽然现实中极少见),靠前的赢。
use std::sync::Arc;
use super::{CtxBuilder, DefaultCtx};
use crate::config::provider::ProviderConfig;
/// 给定 provider config 返回对应的 [`CtxBuilder`]。
///
/// 由 [`crate::agent::AgentLoop::new`] 在会话开始时调用一次,
/// 以及由 `AgentCommand::ReloadConfig` 在用户切模型时重新调用。
///
/// 返回 `Arc` 而非 `Box`:AgentLoop 与它持有的 `TurnRunner` 共享
/// 同一个 ctx 实例,确保 datalog build_messages 和 runner 实际发送
/// 走同一条 ctx 路径(不会因为一边走 trait 派发、另一边走自由函数
/// 而漂移)。ReloadConfig 重建时两处一起更新。
pub fn for_provider(provider: &ProviderConfig) -> Arc<dyn CtxBuilder> {
// ── 规则表(按注册顺序匹配)─────────────────────────
// Ollama: 本地小窗口模型
if provider.provider_type == "ollama" {
return Arc::new(super::ollama::OllamaCtx::new(provider));
}
// (未来其它模型在此插入 —— Claude / GPT-4 / 自定义微调等)
//
// 示例:
// if provider.model.starts_with("claude-") {
// return Arc::new(super::claude::ClaudeCtx::new(provider));
// }
// ── Fallback ────────────────────────────────────────
Arc::new(DefaultCtx::new(provider))
}
#[cfg(test)]
mod tests {
use super::*;
fn provider(ptype: &str, model: &str, ctx: usize) -> ProviderConfig {
ProviderConfig {
provider_type: ptype.into(),
api_key: None,
model: model.into(),
base_url: None,
system_prompt: None,
user_agent: None,
context_window: ctx,
max_tokens: None,
thinking_type: None,
thinking_keep: None,
reasoning_history: None,
thinking_enabled: None,
thinking_budget: None,
skip_tls_verify: false,
ephemeral: false,
}
}
#[test]
fn resolves_ollama_by_provider_type() {
let p = provider("ollama", "llama3", 8_000);
assert_eq!(for_provider(&p).name(), "ollama");
}
#[test]
fn resolves_default_for_unknown_provider() {
let p = provider("openai", "gpt-4o", 128_000);
assert_eq!(for_provider(&p).name(), "default");
let p = provider("anthropic", "claude-3-5-sonnet", 200_000);
assert_eq!(for_provider(&p).name(), "default");
let p = provider("", "", 0);
assert_eq!(for_provider(&p).name(), "default");
}
#[test]
fn ollama_rule_matches_any_model_under_ollama_provider() {
// 不管 model 字段是什么,provider_type == "ollama" 就走 Ollama
for model in ["llama3-8b", "qwen2.5-coder", "deepseek-coder-v2", ""] {
let p = provider("ollama", model, 8_000);
assert_eq!(for_provider(&p).name(), "ollama", "model={}", model);
}
}
}