systemprompt_cli/commands/admin/setup/
services_files.rs1use std::path::Path;
14
15use anyhow::{Context, Result};
16use serde::Serialize;
17use systemprompt_identifiers::ProviderId;
18use systemprompt_logging::CliService;
19use systemprompt_models::services::{GatewayConfigSpec, GatewayState, ProviderRegistry};
20
21use super::catalog;
22use super::secrets::SecretsData;
23use crate::commands::admin::config::config_section::{
24 GATEWAY_FILE_RELATIVE, GATEWAY_INCLUDE_RELATIVE, PROVIDERS_FILE_RELATIVE,
25 PROVIDERS_INCLUDE_RELATIVE,
26};
27use crate::commands::admin::config::services_io::append_include;
28
29#[derive(Serialize)]
30struct ProvidersFile {
31 providers: ProviderRegistry,
32}
33
34#[derive(Serialize)]
35struct GatewayFile {
36 gateway: GatewayState,
37}
38
39pub(super) fn seed(
40 services_dir: &Path,
41 secrets: &SecretsData,
42 default_provider: Option<&ProviderId>,
43 force: bool,
44) -> Result<()> {
45 let registry = catalog::build_registry(secrets);
46 let gateway = GatewayState::Spec(GatewayConfigSpec {
47 enabled: true,
48 routes: catalog::build_routes(secrets),
49 default_provider: default_provider.cloned(),
50 ..GatewayConfigSpec::default()
51 });
52
53 registry
54 .validate()
55 .context("generated provider registry failed validation")?;
56 gateway
57 .clone()
58 .into_spec()
59 .resolve()
60 .validate(®istry)
61 .context("generated gateway config failed validation")?;
62
63 let providers_header = "# Provider catalog — models, pricing, capabilities, limits.\n# \
64 Implementation configuration shipped with the deployment; \
65 credentials\n# are named by `api_key_secret` and live in the profile \
66 secret store.\n";
67 let gateway_header = "# Gateway routes — external model patterns onto providers declared \
68 in\n# providers.yaml. Edit with `systemprompt admin config gateway`.\n";
69
70 write_if_absent(
71 services_dir,
72 PROVIDERS_FILE_RELATIVE,
73 providers_header,
74 &ProvidersFile {
75 providers: registry,
76 },
77 force,
78 )?;
79 write_if_absent(
80 services_dir,
81 GATEWAY_FILE_RELATIVE,
82 gateway_header,
83 &GatewayFile { gateway },
84 force,
85 )?;
86
87 let root = services_dir.join("config").join("config.yaml");
88 if root.exists() {
89 append_include(&root, PROVIDERS_INCLUDE_RELATIVE)?;
90 append_include(&root, GATEWAY_INCLUDE_RELATIVE)?;
91 } else {
92 CliService::warning(&format!(
93 "{} not found; list {PROVIDERS_INCLUDE_RELATIVE} and {GATEWAY_INCLUDE_RELATIVE} in its \
94 includes when you create it",
95 root.display()
96 ));
97 }
98 Ok(())
99}
100
101fn write_if_absent<T: Serialize>(
102 services_dir: &Path,
103 relative: &str,
104 header: &str,
105 body: &T,
106 force: bool,
107) -> Result<()> {
108 let path = services_dir.join(relative);
109 if path.exists() && !force {
110 CliService::info(&format!("Keeping existing {}", path.display()));
111 return Ok(());
112 }
113 if let Some(parent) = path.parent() {
114 std::fs::create_dir_all(parent)
115 .with_context(|| format!("Failed to create {}", parent.display()))?;
116 }
117 let yaml = serde_yaml::to_string(body).context("Failed to serialize services file")?;
118 std::fs::write(&path, format!("{header}{yaml}"))
119 .with_context(|| format!("Failed to write {}", path.display()))?;
120 CliService::success(&format!("Saved {}", path.display()));
121 Ok(())
122}