use rusqlite::{OptionalExtension, params};
use super::decode::decode_json_row;
use super::{PRIMARY_RUNTIME_ID, Storage};
use crate::bridge_protocol::{RuntimeRecord, now_millis};
impl Storage {
pub fn ensure_primary_runtime(
&self,
codex_home: Option<String>,
codex_binary: String,
) -> anyhow::Result<RuntimeRecord> {
if let Some(existing) = self.get_runtime(PRIMARY_RUNTIME_ID)? {
let desired_home = codex_home.or(existing.codex_home.clone());
let desired_binary = if codex_binary.trim().is_empty() {
existing.codex_binary.clone()
} else {
codex_binary
};
let needs_update = existing.codex_home != desired_home
|| existing.codex_binary != desired_binary
|| !existing.is_primary
|| !existing.auto_start;
if !needs_update {
return Ok(existing);
}
let updated = RuntimeRecord {
codex_home: desired_home,
codex_binary: desired_binary,
is_primary: true,
auto_start: true,
updated_at_ms: now_millis(),
..existing
};
self.upsert_runtime(&updated)?;
return Ok(updated);
}
let now = now_millis();
let record = RuntimeRecord {
runtime_id: PRIMARY_RUNTIME_ID.to_string(),
display_name: "Primary".to_string(),
codex_home,
codex_binary,
is_primary: true,
auto_start: true,
created_at_ms: now,
updated_at_ms: now,
};
self.upsert_runtime(&record)?;
Ok(record)
}
pub fn list_runtimes(&self) -> anyhow::Result<Vec<RuntimeRecord>> {
let conn = self.connect()?;
let mut stmt = conn.prepare(
"SELECT raw_json
FROM runtimes
ORDER BY is_primary DESC, created_at_ms ASC",
)?;
let rows = stmt.query_map([], |row| {
let raw: String = row.get(0)?;
decode_json_row(raw)
})?;
Ok(rows.collect::<rusqlite::Result<Vec<_>>>()?)
}
pub fn get_runtime(&self, runtime_id: &str) -> anyhow::Result<Option<RuntimeRecord>> {
let conn = self.connect()?;
let record = conn
.query_row(
"SELECT raw_json FROM runtimes WHERE runtime_id = ?1",
params![runtime_id],
|row| {
let raw: String = row.get(0)?;
decode_json_row(raw)
},
)
.optional()?;
Ok(record)
}
pub fn upsert_runtime(&self, runtime: &RuntimeRecord) -> anyhow::Result<()> {
let conn = self.connect()?;
conn.execute(
"INSERT INTO runtimes (
runtime_id, display_name, codex_home, codex_binary, is_primary,
auto_start, created_at_ms, updated_at_ms, raw_json
)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)
ON CONFLICT(runtime_id) DO UPDATE SET
display_name = excluded.display_name,
codex_home = excluded.codex_home,
codex_binary = excluded.codex_binary,
is_primary = excluded.is_primary,
auto_start = excluded.auto_start,
created_at_ms = excluded.created_at_ms,
updated_at_ms = excluded.updated_at_ms,
raw_json = excluded.raw_json",
params![
runtime.runtime_id,
runtime.display_name,
runtime.codex_home,
runtime.codex_binary,
if runtime.is_primary { 1_i64 } else { 0_i64 },
if runtime.auto_start { 1_i64 } else { 0_i64 },
runtime.created_at_ms,
runtime.updated_at_ms,
serde_json::to_string(runtime)?,
],
)?;
Ok(())
}
pub fn remove_runtime(&self, runtime_id: &str) -> anyhow::Result<()> {
let conn = self.connect()?;
conn.execute(
"DELETE FROM runtimes WHERE runtime_id = ?1",
params![runtime_id],
)?;
Ok(())
}
}