use std::env;
use std::error::Error;
use std::sync::Arc;
use std::time::Duration;
use ziyuan_llm::OpenAiCompatible;
use ziyuan_runtime::{AgentBackend, DemoBackend, Limits, WorkerBackend};
pub(crate) const MAX_AGENT_STEPS: usize = 12;
pub(crate) const DEFAULT_LIMITS: Limits = Limits {
max_concurrent: 4,
timeout: Duration::from_secs(300),
};
pub(crate) fn login(models_only: bool) -> Result<(), ziyuan_runtime::BoxError> {
use ziyuan_llm::chatgpt::{API_BASE, Account, USAGE_URL};
let account = Account::from_env()?;
if !models_only {
let (creds, first) = account.login(|url| {
println!("请在浏览器中登录 ChatGPT 并授权紫垣:\n{url}");
open_browser(url);
})?;
println!("已登录 ChatGPT:{}", creds.email);
if first {
println!("已用 ChatGPT 订阅:紫垣的模型调用计入你的订阅额度。管理用量:{USAGE_URL}");
}
}
let base = env::var("ZIYUAN_LLM_BASE_URL")
.ok()
.filter(|u| !u.is_empty())
.unwrap_or_else(|| API_BASE.into());
println!("可选模型(设 ZIYUAN_LLM_PROVIDER=chatgpt 与 ZIYUAN_LLM_MODEL=<模型>):");
for (slug, name) in account.models(&base)? {
println!(" {slug:<24}{name}");
}
Ok(())
}
pub(crate) fn logout() -> Result<(), ziyuan_runtime::BoxError> {
if ziyuan_llm::chatgpt::Account::from_env()?.logout()? {
println!("已退出 ChatGPT 订阅登录。");
} else {
println!("已清空本机令牌;授权服务未确认撤销,可在 ChatGPT 设置中断开紫垣。");
}
Ok(())
}
pub(crate) fn open_browser(url: &str) {
let mut command = if cfg!(target_os = "macos") {
std::process::Command::new("open")
} else if cfg!(windows) {
let mut c = std::process::Command::new("rundll32");
c.arg("url.dll,FileProtocolHandler");
c
} else {
std::process::Command::new("xdg-open")
};
let _ = command
.arg(url)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn();
}
pub(crate) fn env_model() -> Option<OpenAiCompatible> {
OpenAiCompatible::from_env().ok().flatten()
}
pub(crate) fn plan_free(model: Option<&OpenAiCompatible>) -> bool {
!model.is_some_and(OpenAiCompatible::uses_chatgpt_plan)
}
pub(crate) fn model_label() -> String {
env_model().map_or("演示(未接模型)".into(), |m| m.model().to_owned())
}
pub(crate) fn worker_backend() -> Result<Arc<dyn WorkerBackend>, Box<dyn Error>> {
let Some(model) = OpenAiCompatible::from_env().map_err(|e| e as Box<dyn Error>)? else {
return Ok(Arc::new(DemoBackend));
};
let plan = if model.uses_chatgpt_plan() {
",计入 ChatGPT 订阅额度"
} else {
""
};
eprintln!(
"执行后端:{}{plan}(工具与查阅范围由各诏令的玉玺决定)",
model.model()
);
let catalog = Box::new(model.catalog());
let steps = match env::var("ZIYUAN_MAX_STEPS") {
Ok(n) => n
.parse()
.ok()
.filter(|&n| n > 0)
.ok_or("ZIYUAN_MAX_STEPS 须为正整数")?,
Err(_) => MAX_AGENT_STEPS,
};
let context_tokens = match env::var("ZIYUAN_CONTEXT_TOKENS") {
Ok(n) => n
.parse::<u64>()
.ok()
.filter(|&n| n > 0)
.ok_or("ZIYUAN_CONTEXT_TOKENS 须为正整数(旧工具输出的节略阈值)")?,
Err(env::VarError::NotPresent) => ziyuan_runtime::agent_loop::CONTEXT_TOKENS,
Err(_) => return Err("ZIYUAN_CONTEXT_TOKENS 须为有效 Unicode 正整数".into()),
};
eprintln!("上下文节略阈值:{context_tokens} tokens(不是模型窗口上限)");
Ok(Arc::new(
AgentBackend::new(Box::new(model), env::current_dir()?, steps)
.with_context_tokens(context_tokens)
.with_catalog(catalog)
.with_test_command(env::var("ZIYUAN_TEST_COMMAND").ok()),
))
}