Something went wrong. Try again.
Personal fork of prime agent. Mirror of: https://mnemosyne.sigilzero.dev/sigilzero/prime-agent
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252//! The runtime boundary: typed execution modes and options. Crates that provide the//! real runtime plug in behind [`Runtime::run`] at merge time.
use std::path::PathBuf;
use crate::args::{Args, AutonomousConfig, Mode};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum AppMode { Interactive, Print, Json, Rpc, Acp, Daemon,}
impl AppMode { /// Resolve the execution mode from parsed args and stdin TTY /// state. #[must_use] pub fn resolve(parsed: &Args, stdin_is_tty: bool) -> AppMode { match parsed.mode { Some(Mode::Daemon) => AppMode::Daemon, Some(Mode::Rpc) => AppMode::Rpc, Some(Mode::Acp) => AppMode::Acp, Some(Mode::Json) => AppMode::Json, _ => { if parsed.print || !stdin_is_tty { AppMode::Print } else { AppMode::Interactive } } } }
#[must_use] pub fn as_str(&self) -> &'static str { match self { AppMode::Interactive => "interactive", AppMode::Print => "print", AppMode::Json => "json", AppMode::Rpc => "rpc", AppMode::Acp => "acp", AppMode::Daemon => "daemon", } }
#[must_use] pub fn print_output_mode(&self) -> Mode { match self { AppMode::Json => Mode::Json, _ => Mode::Text, } }}
#[derive(Debug, Clone, PartialEq, Eq)]pub struct InitialGoal { pub objective: String, pub token_budget: Option<u32>,}
/// The typed per-session runtime configuration: the API boundary the/// pa-core/pa-ai crates consume at merge time.#[derive(Debug, Clone, Default)]#[allow(clippy::struct_excessive_bools)] // the mirrored `AgentSessionRuntimeConfig` API shape is deliberatepub struct RuntimeConfig { pub cwd: PathBuf, pub agent_dir: PathBuf, pub session_dir: Option<PathBuf>, pub provider: Option<String>, pub model: Option<String>, pub api_key: Option<String>, pub system_prompt: Option<String>, pub append_system_prompt: Vec<String>, pub thinking: Option<pa_types::ai::ModelThinkingLevel>, pub models: Option<Vec<String>>, pub skills: Vec<PathBuf>, pub no_skills: bool, pub prompt_templates: Vec<PathBuf>, pub no_prompt_templates: bool, pub themes: Vec<PathBuf>, pub no_themes: bool, pub no_context_files: bool, pub autonomous: Option<AutonomousConfig>, pub initial_goal: Option<InitialGoal>,}
/// Session selection options that stay client-side.#[derive(Debug, Clone, Default)]#[allow(clippy::struct_excessive_bools)] // the selection's flag set is the deliberate client-side surfacepub struct SessionOptions { /// `--continue`/`-c`: the launch surfaces the newest saved session for the cwd /// through the agents view (preselected, never a blind reopen). pub continue_recent: bool, pub resume_bare: bool, pub resume: Option<String>, pub fork: Option<String>, pub no_session: bool, pub session_dir: Option<PathBuf>, /// True when `--cwd` selected the working directory; resumed /// sessions then use that directory instead of the header cwd. pub cwd_from_flag: bool,}
#[derive(Debug, Clone)]pub struct RunOptions { pub app_mode: AppMode, pub config: RuntimeConfig, pub session: SessionOptions, pub messages: Vec<String>, pub file_args: Vec<String>, pub daemon_socket: Option<String>, pub list_models: Option<Option<String>>, /// The combined first prompt (stdin + @file text + first message). pub initial_message: Option<String>, /// The `@file` image attachments for the initial prompt; only the /// non-interactive prompt path sends them (the interactive arm is unwired). pub initial_images: Vec<pa_agent::types::ImageContent>, pub verbose: bool, pub offline: bool, pub agents_view_requested: bool, pub attach_agent: Option<String>,}
/// A missing runtime subsystem, reported as a typed error instead of faked/// output. The enum makes the merge-time work explicit.#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum MissingSubsystem { SessionEngine, ModelRegistry, PackageManager,}
impl MissingSubsystem { #[must_use] pub fn subsystem_name(&self) -> &'static str { match self { MissingSubsystem::SessionEngine => "the session engine (pa-core)", MissingSubsystem::ModelRegistry => "the model registry (pa-ai)", MissingSubsystem::PackageManager => "the capability package manager (pa-core)", } }
#[must_use] pub fn error_message(&self) -> String { format!( "this invocation needs {}, which is not linked into the binary yet", self.subsystem_name() ) }}
impl std::fmt::Display for MissingSubsystem { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(self.subsystem_name()) }}
impl std::error::Error for MissingSubsystem {}
/// The runtime boundary. `run` executes the requested mode; the current build has/// only [`UnavailableRuntime`] (typed [`MissingSubsystem`] errors).pub trait Runtime { /// Execute the requested mode. /// /// # Errors /// Returns a [`MissingSubsystem`] error naming the subsystem the /// requested mode needs when it is not linked into the binary. fn run(&self, options: &RunOptions) -> Result<i32, MissingSubsystem>;}
/// The merge-time placeholder runtime: it never fakes output, it fails loudly/// with the precise missing subsystem for the requested mode.#[derive(Debug, Clone, Copy, Default)]pub struct UnavailableRuntime;
impl Runtime for UnavailableRuntime { fn run(&self, options: &RunOptions) -> Result<i32, MissingSubsystem> { if options.list_models.is_some() { return Err(MissingSubsystem::ModelRegistry); } let _ = options.app_mode; Err(MissingSubsystem::SessionEngine) }}
/// Build the runtime config from parsed args, mirroring `runtimeConfigFromArgs`.pub fn runtime_config_from_args( parsed: &Args, cwd: PathBuf, agent_dir: PathBuf, session_dir: Option<PathBuf>,) -> RuntimeConfig { // Only npm:/git:/github:/http(s):/ssh: sources are not local; // everything else resolves against the session cwd. let is_local_path = |value: &str| { let trimmed = value.trim(); !(trimmed.starts_with("npm:") || trimmed.starts_with("git:") || trimmed.starts_with("github:") || trimmed.starts_with("http:") || trimmed.starts_with("https:") || trimmed.starts_with("ssh:")) }; let resolve_cli_path = |paths: &[String]| -> Vec<PathBuf> { paths .iter() .map(|value| { if is_local_path(value) { if std::path::Path::new(value).is_absolute() { PathBuf::from(value) } else { cwd.join(value) } } else { PathBuf::from(value) } }) .collect() }; let skills = resolve_cli_path(&parsed.skills); let prompt_templates = resolve_cli_path(&parsed.prompt_templates); let themes = resolve_cli_path(&parsed.themes); RuntimeConfig { cwd, agent_dir, session_dir, provider: parsed.provider.clone(), model: parsed.model.clone(), api_key: parsed.api_key.clone(), system_prompt: parsed.system_prompt.clone(), append_system_prompt: parsed.append_system_prompt.clone(), thinking: parsed.thinking, models: parsed.models.clone(), skills, no_skills: parsed.no_skills, prompt_templates, no_prompt_templates: parsed.no_prompt_templates, themes, no_themes: parsed.no_themes, no_context_files: parsed.no_context_files, autonomous: AutonomousConfig::from_args(parsed), initial_goal: parsed.goal.as_ref().map(|objective| InitialGoal { objective: objective.clone(), token_budget: parsed.goal_token_budget, }), }}