Route strategic smoke through runtime provider

This commit is contained in:
Meta Vens 2026-08-26 23:36:33 +02:00
commit 41f921cc47
4 changed files with 148 additions and 87 deletions

View file

@ -1,25 +1,20 @@
#[cfg(not(windows))]
fn main() -> anyhow::Result<()> {
anyhow::bail!("the live strategic smoke uses Starfire's DPAPI credential")
}
#[cfg(windows)]
#[tokio::main]
async fn main() -> anyhow::Result<()> {
use chrono::Utc;
use ghostlight_dungeon::{
domain::{TickSource, WorldCommand},
kernel::{CommandResult, WorldKernel},
model::{DeepSeekPort, ModelPort},
model_runtime::ModelRuntimeSelection,
persistence::CampaignStore,
scheduler::propose_resolution_wave,
turn::SnapshotPermit,
};
use std::{path::PathBuf, sync::Arc, time::Instant};
let secret = std::env::var_os("GHOSTLIGHT_DEEPSEEK_BLOB")
let runtime_root = std::env::var_os("GHOSTLIGHT_DUNGEON_ROOT")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from(r"F:\GameCult\GhostlightDungeon\secrets\deepseek.dpapi"));
.unwrap_or_else(default_runtime_root);
let model_selection = ModelRuntimeSelection::from_environment(&runtime_root)?;
let scenario_id = std::env::var("GHOSTLIGHT_LIVE_FIRE_SCENARIO")
.unwrap_or_else(|_| "strategic-default".into());
let pressure = std::env::var("GHOSTLIGHT_STRATEGIC_PRESSURE").unwrap_or_else(|_| {
@ -53,7 +48,13 @@ async fn main() -> anyhow::Result<()> {
.clone();
let store = CampaignStore::open(root.join("campaign.cc"))?;
store.create_unadmitted_fixture_campaign(&campaign, &[], &[])?;
let model: Arc<dyn ModelPort> = Arc::new(DeepSeekPort::from_runtime_secret(secret)?);
let model = model_selection.open()?.ok_or_else(|| {
anyhow::anyhow!(
"{} credential is unavailable at {}",
model_selection.provider,
model_selection.credential_path.display()
)
})?;
let started = Instant::now();
let output = propose_resolution_wave(
model,
@ -135,6 +136,7 @@ async fn main() -> anyhow::Result<()> {
"pressure":pressure,
"campaign_id":campaign.id,
"elapsed_seconds":started.elapsed().as_secs_f64(),
"model_runtime":model_selection.status("configured"),
"model_receipt_hash":output.aggregate_receipt_hash,
"model_stage_receipts":output.stages.iter().map(|stage|&stage.receipt).collect::<Vec<_>>(),
"plan":plan,
@ -154,7 +156,17 @@ async fn main() -> anyhow::Result<()> {
Ok(())
}
#[cfg(windows)]
fn default_runtime_root() -> std::path::PathBuf {
#[cfg(windows)]
{
std::path::PathBuf::from(r"F:\GameCult\GhostlightDungeon")
}
#[cfg(not(windows))]
{
std::path::PathBuf::from("/var/lib/gamecult/ghostlight-dungeon")
}
}
fn strategic_campaign() -> ghostlight_dungeon::domain::Campaign {
use chrono::{Duration, Utc};
use ghostlight_dungeon::domain::*;

View file

@ -14,6 +14,7 @@ pub mod legacy_transition;
pub mod mesh;
pub mod model;
pub mod model_connector;
pub mod model_runtime;
pub mod newspaper;
pub mod outcome;
pub mod persistence;

View file

@ -28,10 +28,8 @@ use ghostlight_dungeon::{
CampaignMeshSnapshot, MeshPublisher, MeshRuntimeIdentity, PROVIDER_ID as EVE_PROVIDER_ID,
SURFACE_ID as EVE_SURFACE_ID, SessionZeroMeshSnapshot,
},
model::{
DeepSeekPort, MODEL_CAPABLE, MODEL_FAST, ModelPort, ModelRuntimeStatus, OpenRouterPort,
},
model_connector::CodexConnectorModelPort,
model::{MODEL_CAPABLE, MODEL_FAST, ModelPort, ModelRuntimeStatus},
model_runtime::ModelRuntimeSelection,
persistence::CampaignStore,
persona::PersonaProjectionEngine,
registry::{CampaignRegistry, CampaignRuntime},
@ -302,79 +300,29 @@ async fn main() -> anyhow::Result<()> {
for (account_hash, campaign_id) in &legacy_auth_state.session_campaigns {
app_sessions.migrate_preference(account_hash, *campaign_id)?;
}
let provider_name = std::env::var("GHOSTLIGHT_MODEL_PROVIDER")
.unwrap_or_else(|_| "deepseek".into())
.to_ascii_lowercase();
let (default_fast_model, default_capable_model, default_secret_name) =
match provider_name.as_str() {
"deepseek" => ("deepseek-v4-flash", "deepseek-v4-pro", "deepseek.dpapi"),
"openrouter" => ("stealth/ox-alpha", "stealth/ox-alpha", "openrouter.key"),
"codex-connector" => ("gpt-5.6-luna", "gpt-5.6-luna", "codex-connector.key"),
unsupported => anyhow::bail!("unsupported model provider {unsupported}"),
let model_selection = ModelRuntimeSelection::from_environment(&runtime_root)?;
let (model_status, compiler, assessor, shared_model) =
if let Some(provider) = model_selection.open()? {
let vault_endpoint = std::env::var("GHOSTLIGHT_VAULT_MCP_URL")
.unwrap_or_else(|_| "http://127.0.0.1:17875/mcp".into());
(
model_selection.status("configured"),
Some(Arc::new(WorldCompiler::new(
Arc::new(VoidBotMcpVault::new(vault_endpoint)),
provider.clone(),
MODEL_FAST,
MODEL_CAPABLE,
))),
Some(Arc::new(ActionAssessor::with_models(
provider.clone(),
MODEL_FAST,
MODEL_CAPABLE,
))),
Some(provider),
)
} else {
(model_selection.status("missing-secret"), None, None, None)
};
let fast_model =
std::env::var("GHOSTLIGHT_MODEL_FAST").unwrap_or_else(|_| default_fast_model.into());
let capable_model =
std::env::var("GHOSTLIGHT_MODEL_CAPABLE").unwrap_or_else(|_| default_capable_model.into());
let connector_max_concurrent_requests =
std::env::var("GHOSTLIGHT_CODEX_CONNECTOR_MAX_CONCURRENT_REQUESTS")
.unwrap_or_else(|_| "8".to_string())
.parse::<usize>()
.context("GHOSTLIGHT_CODEX_CONNECTOR_MAX_CONCURRENT_REQUESTS must be an integer")?;
let secret_path = std::env::var_os("GHOSTLIGHT_MODEL_CREDENTIAL")
.map(PathBuf::from)
.unwrap_or_else(|| runtime_root.join("secrets").join(default_secret_name));
let configured_status = |readiness: String| ModelRuntimeStatus {
provider: provider_name.clone(),
fast_model: fast_model.clone(),
capable_model: capable_model.clone(),
readiness,
};
let (model_status, compiler, assessor, shared_model) = if secret_path.is_file() {
let provider: Arc<dyn ModelPort> = match provider_name.as_str() {
"deepseek" => Arc::new(DeepSeekPort::from_runtime_secret_with_models(
&secret_path,
fast_model.clone(),
capable_model.clone(),
)?),
"openrouter" => Arc::new(OpenRouterPort::from_runtime_secret(
&secret_path,
fast_model.clone(),
capable_model.clone(),
)?),
"codex-connector" => Arc::new(CodexConnectorModelPort::from_runtime_secret(
std::env::var("GHOSTLIGHT_MODEL_CONNECTOR")
.unwrap_or_else(|_| "127.0.0.1:4103".to_string())
.parse()?,
&secret_path,
std::env::var("GHOSTLIGHT_RUNTIME_ID")
.unwrap_or_else(|_| "ghostlight-dungeon-yggdrasil".to_string()),
fast_model.clone(),
capable_model.clone(),
connector_max_concurrent_requests,
)?),
_ => unreachable!("provider name was validated above"),
};
let vault_endpoint = std::env::var("GHOSTLIGHT_VAULT_MCP_URL")
.unwrap_or_else(|_| "http://127.0.0.1:17875/mcp".into());
(
configured_status("configured".into()),
Some(Arc::new(WorldCompiler::new(
Arc::new(VoidBotMcpVault::new(vault_endpoint)),
provider.clone(),
MODEL_FAST,
MODEL_CAPABLE,
))),
Some(Arc::new(ActionAssessor::with_models(
provider.clone(),
MODEL_FAST,
MODEL_CAPABLE,
))),
Some(provider),
)
} else {
(configured_status("missing-secret".into()), None, None, None)
};
let mesh_target = std::env::var("GHOSTLIGHT_ODIN_RUDP")
.ok()
.map(|value| value.parse())

View file

@ -0,0 +1,100 @@
use crate::{
model::{DeepSeekPort, ModelPort, ModelRuntimeStatus, OpenRouterPort},
model_connector::CodexConnectorModelPort,
};
use anyhow::{Context, Result, bail};
use std::{
net::SocketAddr,
path::{Path, PathBuf},
sync::Arc,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ModelRuntimeSelection {
pub provider: String,
pub fast_model: String,
pub capable_model: String,
pub credential_path: PathBuf,
pub connector_endpoint: SocketAddr,
pub runtime_id: String,
pub connector_max_concurrent_requests: usize,
}
impl ModelRuntimeSelection {
pub fn from_environment(runtime_root: impl AsRef<Path>) -> Result<Self> {
let provider = std::env::var("GHOSTLIGHT_MODEL_PROVIDER")
.unwrap_or_else(|_| "deepseek".into())
.to_ascii_lowercase();
let (default_fast_model, default_capable_model, default_secret_name) =
match provider.as_str() {
"deepseek" => ("deepseek-v4-flash", "deepseek-v4-pro", "deepseek.dpapi"),
"openrouter" => ("stealth/ox-alpha", "stealth/ox-alpha", "openrouter.key"),
"codex-connector" => ("gpt-5.6-luna", "gpt-5.6-luna", "codex-connector.key"),
unsupported => bail!("unsupported model provider {unsupported}"),
};
Ok(Self {
provider,
fast_model: std::env::var("GHOSTLIGHT_MODEL_FAST")
.unwrap_or_else(|_| default_fast_model.into()),
capable_model: std::env::var("GHOSTLIGHT_MODEL_CAPABLE")
.unwrap_or_else(|_| default_capable_model.into()),
credential_path: std::env::var_os("GHOSTLIGHT_MODEL_CREDENTIAL")
.map(PathBuf::from)
.unwrap_or_else(|| {
runtime_root
.as_ref()
.join("secrets")
.join(default_secret_name)
}),
connector_endpoint: std::env::var("GHOSTLIGHT_MODEL_CONNECTOR")
.unwrap_or_else(|_| "127.0.0.1:4103".to_string())
.parse()
.context("GHOSTLIGHT_MODEL_CONNECTOR must be a socket address")?,
runtime_id: std::env::var("GHOSTLIGHT_RUNTIME_ID")
.unwrap_or_else(|_| "ghostlight-dungeon-yggdrasil".to_string()),
connector_max_concurrent_requests: std::env::var(
"GHOSTLIGHT_CODEX_CONNECTOR_MAX_CONCURRENT_REQUESTS",
)
.unwrap_or_else(|_| "8".to_string())
.parse::<usize>()
.context("GHOSTLIGHT_CODEX_CONNECTOR_MAX_CONCURRENT_REQUESTS must be an integer")?,
})
}
pub fn status(&self, readiness: impl Into<String>) -> ModelRuntimeStatus {
ModelRuntimeStatus {
provider: self.provider.clone(),
fast_model: self.fast_model.clone(),
capable_model: self.capable_model.clone(),
readiness: readiness.into(),
}
}
pub fn open(&self) -> Result<Option<Arc<dyn ModelPort>>> {
if !self.credential_path.is_file() {
return Ok(None);
}
let provider: Arc<dyn ModelPort> = match self.provider.as_str() {
"deepseek" => Arc::new(DeepSeekPort::from_runtime_secret_with_models(
&self.credential_path,
self.fast_model.clone(),
self.capable_model.clone(),
)?),
"openrouter" => Arc::new(OpenRouterPort::from_runtime_secret(
&self.credential_path,
self.fast_model.clone(),
self.capable_model.clone(),
)?),
"codex-connector" => Arc::new(CodexConnectorModelPort::from_runtime_secret(
self.connector_endpoint,
&self.credential_path,
self.runtime_id.clone(),
self.fast_model.clone(),
self.capable_model.clone(),
self.connector_max_concurrent_requests,
)?),
unsupported => bail!("unsupported model provider {unsupported}"),
};
Ok(Some(provider))
}
}