use anyhow::Result;
use theway_core::ThinkingLevel;
use theway_core::executor::ExecutorKind;
use theway_transport::config::ModelDefault;
use super::DaemonOptions;
use crate::startup_config::StartupConfig;
pub(super) fn startup_config_from_options(options: &DaemonOptions) -> Result<StartupConfig> {
let initial_settings_payload = theway_transport::wire::WireDaemonConfig::default();
let mut startup = StartupConfig::from_wire(&initial_settings_payload);
if let Some(secs) = options.trigger_poll_secs {
startup.trigger_poll_secs = secs;
}
if let Some(raw) = options.executor_kind.as_deref() {
startup.executor_kind =
crate::executor::parse_executor_kind(raw).map_err(anyhow::Error::msg)?;
}
if options.no_tgrep {
startup.tgrep_enabled = false;
}
if let Some(api_key) = options.api_key.as_deref() {
startup.api_key = Some(api_key.to_string());
}
if options.auto_fetch_models {
startup.auto_fetch_models = true;
}
startup.storage_service_addr = options.storage_service_addr.clone();
if options.storage_service_addr.is_some() {
startup.load_local_sources = false;
}
if startup.executor_kind == ExecutorKind::Sandbox {
startup.load_local_sources = false;
}
Ok(startup)
}
pub(super) fn launch_thinking(options: &DaemonOptions, startup: &StartupConfig) -> ThinkingLevel {
if options.thinking == ThinkingLevel::Off {
startup.thinking_level.unwrap_or(ThinkingLevel::Off)
} else {
options.thinking
}
}
pub(crate) async fn provision_model_catalog(
startup: &mut StartupConfig,
cli_base_url: Option<&str>,
configured_api_keys: &crate::stream_auth::ConfiguredApiKeys,
) {
crate::model_defaults::register_models(&startup.models);
match (startup.provider.as_deref(), startup.api_key.as_deref()) {
(Some(provider), Some(api_key)) => {
configured_api_keys
.write()
.expect("configured api keys poisoned")
.insert(provider.to_string(), api_key.to_string());
}
(None, Some(_)) => tracing::warn!(
target: "model",
"`[model] api_key` is set without `[model] provider`; the key is ignored"
),
_ => {}
}
if !startup.auto_fetch_models {
return;
}
let Some(provider) = startup.provider.clone() else {
tracing::warn!(target: "model", "auto-fetch models: `[model] provider` is required");
return;
};
let Some(base_url) = cli_base_url
.map(str::to_string)
.or_else(|| startup.base_url.clone())
else {
tracing::warn!(
target: "model",
"auto-fetch models: `[model] base_url` (or --base-url) is required"
);
return;
};
let api_key = theway_transport::auth::AuthStore::load()
.unwrap_or_default()
.resolve_for_provider_with(&provider, startup.api_key.as_deref());
match crate::model_fetch::fetch_models(&base_url, api_key.as_deref(), &provider).await {
Ok(models) if !models.is_empty() => {
let first = models[0].id.clone();
crate::model_defaults::register_models(&models);
tracing::info!(
target: "model",
"auto-fetched {} model(s) from {base_url}",
models.len()
);
if startup.model_default.is_none() {
startup.model_default = Some(ModelDefault {
provider,
model: first,
});
}
}
Ok(_) => {
tracing::warn!(target: "model", "auto-fetch models from {base_url}: empty catalog")
}
Err(err) => tracing::warn!(target: "model", "auto-fetch models from {base_url}: {err}"),
}
}
pub(crate) async fn resolve_startup_model(
cli_provider: Option<&str>,
cli_model: Option<&str>,
cli_base_url: Option<&str>,
startup: &StartupConfig,
) -> Result<Option<theway_llm_provider::Model>> {
crate::model_defaults::register_ds4_default(cli_base_url);
let cli_overrides_model = cli_provider.is_some() || cli_model.is_some();
let (provider_override, model_override) = if cli_overrides_model {
(cli_provider, cli_model)
} else {
match &startup.model_default {
Some(default) => (
Some(default.provider.as_str()),
Some(default.model.as_str()),
),
None => (None, None),
}
};
let Some((provider, id)) = provider_override.zip(model_override) else {
return Ok(None);
};
let mut model = crate::model::auto_detect_model(Some(provider), Some(id))?;
if let Some(base_url) = cli_base_url
.map(str::trim)
.filter(|url| !url.is_empty())
.or(startup.base_url.as_deref())
{
model.base_url = base_url.to_string();
}
Ok(Some(model))
}