Add Oh My Pi support through the OpenAI Responses API
Discover CPA models dynamically and route the managed provider to /v1/responses.
This commit is contained in:
1 parent
aba62fa6bf
commit
d7e39e8fdc
23 files changed
+414
-20
No files matched your search
@@ -87,6 +87,7 @@ fn clients() -> Vec<AgentClient> {
|
||||
"workbuddy",
|
||||
"kimi-code",
|
||||
"grok-build",
|
||||
"omp",
|
||||
]
|
||||
.into_iter()
|
||||
.map(|id| AgentClient::parse(id).unwrap())
|
||||
@@ -417,6 +418,7 @@ fn clear_integration_needs_no_backup_and_removes_state_for_each_client() {
|
||||
AgentClient::WorkBuddy,
|
||||
AgentClient::KimiCode,
|
||||
AgentClient::GrokBuild,
|
||||
AgentClient::Omp,
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|client| client.supported_platform())
|
||||
|
||||
@@ -33,6 +33,7 @@ pub(crate) fn inspect_agent_config_statuses(
|
||||
AgentStatusDetectionTarget::Client(AgentClient::AntigravityCli),
|
||||
AgentStatusDetectionTarget::Client(AgentClient::KimiCode),
|
||||
AgentStatusDetectionTarget::Client(AgentClient::GrokBuild),
|
||||
AgentStatusDetectionTarget::Client(AgentClient::Omp),
|
||||
AgentStatusDetectionTarget::PiProvider,
|
||||
];
|
||||
let queue = Mutex::new(targets.into_iter().enumerate());
|
||||
|
||||
@@ -232,6 +232,23 @@ pub(crate) fn build_agent_updates_with_oauth(
|
||||
after,
|
||||
}])
|
||||
}
|
||||
AgentClient::Omp => {
|
||||
if paths.len() != 2 {
|
||||
return Err("Invalid number of Oh My Pi configuration paths".to_string());
|
||||
}
|
||||
let models_before = read_optional_text(&paths[0])?;
|
||||
let settings_before = read_optional_text(&paths[1])?;
|
||||
Ok(vec![
|
||||
AgentFileUpdate {
|
||||
path: paths[0].clone(),
|
||||
after: build_omp_models_config(models_before.as_deref(), &openai_base, api_key)?,
|
||||
},
|
||||
AgentFileUpdate {
|
||||
path: paths[1].clone(),
|
||||
after: build_omp_settings_config(settings_before.as_deref(), model)?,
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1649,6 +1666,7 @@ pub(crate) fn prepare_agent_managed_removal(
|
||||
AgentClient::AntigravityCli => prepare_antigravity_removal(client, paths),
|
||||
AgentClient::KimiCode => prepare_kimi_code_managed_removal(paths),
|
||||
AgentClient::GrokBuild => prepare_grok_build_managed_removal(paths),
|
||||
AgentClient::Omp => prepare_omp_managed_removal(paths),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2624,7 +2642,7 @@ pub(crate) fn agent_config_semantically_equal(
|
||||
json5::from_str::<serde_json::Value>(actual).ok()
|
||||
== json5::from_str::<serde_json::Value>(expected).ok()
|
||||
}
|
||||
AgentClient::Hermes => {
|
||||
AgentClient::Hermes | AgentClient::Omp => {
|
||||
serde_norway::from_str::<serde_norway::Value>(actual).ok()
|
||||
== serde_norway::from_str::<serde_norway::Value>(expected).ok()
|
||||
}
|
||||
@@ -2703,6 +2721,17 @@ pub(crate) fn build_agent_session_restored_bytes_with_preference(
|
||||
AgentClient::AntigravityCli => restore_antigravity_config(client, path, current, original)?,
|
||||
AgentClient::KimiCode => build_restored_kimi_code_config(current, original)?,
|
||||
AgentClient::GrokBuild => build_restored_grok_build_config(current, original)?,
|
||||
AgentClient::Omp => {
|
||||
let index = paths
|
||||
.iter()
|
||||
.position(|candidate| candidate == path)
|
||||
.ok_or_else(|| "Oh My Pi restoration path mismatch".to_string())?;
|
||||
match index {
|
||||
0 => build_restored_omp_models(current, original)?,
|
||||
1 => build_restored_omp_settings(current, original)?,
|
||||
_ => return Err("Invalid Oh My Pi restoration path index".to_string()),
|
||||
}
|
||||
}
|
||||
};
|
||||
if prefer_exact_original {
|
||||
if let (Some(restored), Some(original), Some(original_bytes)) =
|
||||
@@ -3263,6 +3292,170 @@ pub(crate) fn managed_model_alias(model: &str) -> String {
|
||||
format!("{MANAGED_AGENT_PROVIDER_ID}/{model}")
|
||||
}
|
||||
|
||||
|
||||
pub(crate) fn build_omp_models_config(
|
||||
existing: Option<&str>,
|
||||
base_url: &str,
|
||||
api_key: &str,
|
||||
) -> Result<String, String> {
|
||||
let _ = api_key;
|
||||
render_agent_yaml_mapping_update(existing, "Oh My Pi models.yml", |root| {
|
||||
let providers = root
|
||||
.entry(yaml_key("providers"))
|
||||
.or_insert_with(|| serde_norway::Value::Mapping(serde_norway::Mapping::new()));
|
||||
let providers = providers
|
||||
.as_mapping_mut()
|
||||
.ok_or_else(|| "Oh My Pi providers must be a YAML mapping".to_string())?;
|
||||
let mut provider = serde_norway::Mapping::new();
|
||||
provider.insert(yaml_key("baseUrl"), serde_norway::Value::String(base_url.to_string()));
|
||||
provider.insert(
|
||||
yaml_key("apiKey"),
|
||||
serde_norway::Value::String(OMP_AGENT_API_KEY_ENV.to_string()),
|
||||
);
|
||||
provider.insert(
|
||||
yaml_key("api"),
|
||||
serde_norway::Value::String(OMP_AGENT_API.to_string()),
|
||||
);
|
||||
let mut discovery = serde_norway::Mapping::new();
|
||||
discovery.insert(
|
||||
yaml_key("type"),
|
||||
serde_norway::Value::String("openai-models-list".to_string()),
|
||||
);
|
||||
provider.insert(yaml_key("discovery"), serde_norway::Value::Mapping(discovery));
|
||||
providers.insert(
|
||||
yaml_key(OMP_AGENT_PROVIDER_ID),
|
||||
serde_norway::Value::Mapping(provider),
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn build_omp_settings_config(
|
||||
existing: Option<&str>,
|
||||
model: &str,
|
||||
) -> Result<String, String> {
|
||||
render_agent_yaml_mapping_update(existing, "Oh My Pi config.yml", |root| {
|
||||
let roles = root
|
||||
.entry(yaml_key("modelRoles"))
|
||||
.or_insert_with(|| serde_norway::Value::Mapping(serde_norway::Mapping::new()));
|
||||
let roles = roles
|
||||
.as_mapping_mut()
|
||||
.ok_or_else(|| "Oh My Pi modelRoles must be a YAML mapping".to_string())?;
|
||||
roles.insert(
|
||||
yaml_key("default"),
|
||||
serde_norway::Value::String(format!("{OMP_AGENT_PROVIDER_ID}/{model}")),
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn prepare_omp_managed_removal(paths: &[PathBuf]) -> Result<Images, String> {
|
||||
if paths.len() != 2 {
|
||||
return Err("Invalid number of Oh My Pi configuration paths".to_string());
|
||||
}
|
||||
let mut updates = Vec::new();
|
||||
if paths[0].is_file() {
|
||||
let content = fs::read_to_string(&paths[0]).map_err(|error| {
|
||||
format!("Failed to read Oh My Pi models.yml {}: {error}", path_to_string(&paths[0]))
|
||||
})?;
|
||||
let rendered = render_agent_yaml_mapping_update(Some(&content), "Oh My Pi models.yml", |root| {
|
||||
let mut remove_providers = false;
|
||||
if let Some(providers) = root
|
||||
.get_mut(yaml_key("providers"))
|
||||
.and_then(serde_norway::Value::as_mapping_mut)
|
||||
{
|
||||
providers.remove(yaml_key(OMP_AGENT_PROVIDER_ID));
|
||||
remove_providers = providers.is_empty();
|
||||
}
|
||||
if remove_providers {
|
||||
root.remove(yaml_key("providers"));
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
let root = parse_agent_yaml_mapping(Some(&rendered), "Oh My Pi models.yml")?;
|
||||
updates.push((
|
||||
paths[0].clone(),
|
||||
(!root.is_empty()).then(|| rendered.into_bytes()),
|
||||
));
|
||||
}
|
||||
if paths[1].is_file() {
|
||||
let content = fs::read_to_string(&paths[1]).map_err(|error| {
|
||||
format!("Failed to read Oh My Pi config.yml {}: {error}", path_to_string(&paths[1]))
|
||||
})?;
|
||||
let rendered = render_agent_yaml_mapping_update(Some(&content), "Oh My Pi config.yml", |root| {
|
||||
let mut remove_roles = false;
|
||||
if let Some(roles) = root
|
||||
.get_mut(yaml_key("modelRoles"))
|
||||
.and_then(serde_norway::Value::as_mapping_mut)
|
||||
{
|
||||
if roles
|
||||
.get(yaml_key("default"))
|
||||
.and_then(serde_norway::Value::as_str)
|
||||
.is_some_and(|value| value.starts_with(&format!("{OMP_AGENT_PROVIDER_ID}/")))
|
||||
{
|
||||
roles.remove(yaml_key("default"));
|
||||
}
|
||||
remove_roles = roles.is_empty();
|
||||
}
|
||||
if remove_roles {
|
||||
root.remove(yaml_key("modelRoles"));
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
let root = parse_agent_yaml_mapping(Some(&rendered), "Oh My Pi config.yml")?;
|
||||
updates.push((
|
||||
paths[1].clone(),
|
||||
(!root.is_empty()).then(|| rendered.into_bytes()),
|
||||
));
|
||||
}
|
||||
Ok(updates.into_iter().collect())
|
||||
}
|
||||
|
||||
pub(crate) fn build_restored_omp_models(
|
||||
current: &str,
|
||||
original: Option<&str>,
|
||||
) -> Result<Option<String>, String> {
|
||||
let original_root = parse_agent_yaml_mapping(original, "Original Oh My Pi models.yml")?;
|
||||
let rendered = render_agent_yaml_mapping_update(Some(current), "Current Oh My Pi models.yml", |root| {
|
||||
let original_provider = yaml_mapping_value(&original_root, "providers")
|
||||
.and_then(serde_norway::Value::as_mapping)
|
||||
.and_then(|providers| yaml_mapping_value(providers, OMP_AGENT_PROVIDER_ID))
|
||||
.cloned();
|
||||
let providers = root
|
||||
.entry(yaml_key("providers"))
|
||||
.or_insert_with(|| serde_norway::Value::Mapping(serde_norway::Mapping::new()));
|
||||
let providers = providers
|
||||
.as_mapping_mut()
|
||||
.ok_or_else(|| "Oh My Pi providers must be a YAML mapping".to_string())?;
|
||||
if let Some(provider) = original_provider {
|
||||
providers.insert(yaml_key(OMP_AGENT_PROVIDER_ID), provider);
|
||||
} else {
|
||||
providers.remove(yaml_key(OMP_AGENT_PROVIDER_ID));
|
||||
}
|
||||
if providers.is_empty()
|
||||
&& yaml_mapping_value(&original_root, "providers").is_none()
|
||||
{
|
||||
root.remove(yaml_key("providers"));
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
let root = parse_agent_yaml_mapping(Some(&rendered), "Restored Oh My Pi models.yml")?;
|
||||
Ok((!root.is_empty() || original.is_some()).then_some(rendered))
|
||||
}
|
||||
|
||||
pub(crate) fn build_restored_omp_settings(
|
||||
current: &str,
|
||||
original: Option<&str>,
|
||||
) -> Result<Option<String>, String> {
|
||||
let original_root = parse_agent_yaml_mapping(original, "Original Oh My Pi config.yml")?;
|
||||
let rendered = render_agent_yaml_mapping_update(Some(current), "Current Oh My Pi config.yml", |root| {
|
||||
restore_yaml_key(root, Some(&original_root), "modelRoles");
|
||||
Ok(())
|
||||
})?;
|
||||
let root = parse_agent_yaml_mapping(Some(&rendered), "Restored Oh My Pi config.yml")?;
|
||||
Ok((!root.is_empty() || original.is_some()).then_some(rendered))
|
||||
}
|
||||
|
||||
pub(crate) fn build_kimi_code_agent_config(
|
||||
existing: Option<&str>,
|
||||
base_url: &str,
|
||||
|
||||
@@ -52,6 +52,10 @@ pub(crate) fn agent_config_paths(client: AgentClient, home: &Path) -> Vec<PathBu
|
||||
AgentClient::ZCode => vec![home.join(".zcode/v2").join(ZCODE_PROVIDER_CONFIG_FILE)],
|
||||
AgentClient::KimiCode => vec![kimi_code_home(home).join(KIMI_CODE_CONFIG_FILE)],
|
||||
AgentClient::GrokBuild => vec![grok_build_home(home).join(GROK_BUILD_CONFIG_FILE)],
|
||||
AgentClient::Omp => {
|
||||
let directory = omp_agent_directory(home);
|
||||
vec![directory.join(OMP_MODELS_FILE), directory.join(OMP_SETTINGS_FILE)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +111,10 @@ pub(crate) fn grok_build_home(home: &Path) -> PathBuf {
|
||||
agent_configuration_environment("GROK_HOME").unwrap_or_else(|| home.join(".grok"))
|
||||
}
|
||||
|
||||
pub(crate) fn omp_agent_directory(home: &Path) -> PathBuf {
|
||||
agent_configuration_environment("PI_CODING_AGENT_DIR").unwrap_or_else(|| home.join(".omp/agent"))
|
||||
}
|
||||
|
||||
pub(crate) fn deepseek_harness_home(home: &Path) -> PathBuf {
|
||||
agent_configuration_environment("DSH_HOME").unwrap_or_else(|| home.join(".dsh"))
|
||||
}
|
||||
@@ -1326,6 +1334,8 @@ pub(crate) fn inspect_agent_managed_config(
|
||||
.map(|(configured, model)| (configured, model, false)),
|
||||
AgentClient::GrokBuild => inspect_grok_build_agent_config(&paths[0], port, api_key)
|
||||
.map(|(configured, model)| (configured, model, false)),
|
||||
AgentClient::Omp => inspect_omp_agent_config(paths, port, api_key)
|
||||
.map(|(configured, model)| (configured, model, false)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1561,6 +1571,7 @@ pub(crate) fn agent_has_managed_marker(
|
||||
Some("models"),
|
||||
"default",
|
||||
),
|
||||
AgentClient::Omp => omp_has_managed_marker(paths),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2452,6 +2463,9 @@ pub(crate) fn find_agent_executable(client: AgentClient, home: &Path) -> Option<
|
||||
if client == AgentClient::GrokBuild {
|
||||
return find_grok_build_executable(home);
|
||||
}
|
||||
if client == AgentClient::Omp {
|
||||
return find_omp_executable(home);
|
||||
}
|
||||
if client == AgentClient::OpenCode {
|
||||
return find_opencode_executable(home);
|
||||
}
|
||||
@@ -3453,6 +3467,81 @@ pub(crate) fn inspect_zcode_agent_config(
|
||||
Ok((configured, model))
|
||||
}
|
||||
|
||||
|
||||
pub(crate) fn omp_provider_mapping(root: &serde_norway::Mapping) -> Option<&serde_norway::Mapping> {
|
||||
yaml_mapping_value(root, "providers")
|
||||
.and_then(serde_norway::Value::as_mapping)
|
||||
.and_then(|providers| yaml_mapping_value(providers, OMP_AGENT_PROVIDER_ID))
|
||||
.and_then(serde_norway::Value::as_mapping)
|
||||
}
|
||||
|
||||
pub(crate) fn omp_selected_model(root: &serde_norway::Mapping) -> Option<String> {
|
||||
let selector = yaml_mapping_value(root, "modelRoles")
|
||||
.and_then(serde_norway::Value::as_mapping)
|
||||
.and_then(|roles| yaml_mapping_value(roles, "default"))
|
||||
.and_then(serde_norway::Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())?;
|
||||
let prefix = format!("{OMP_AGENT_PROVIDER_ID}/");
|
||||
selector.strip_prefix(&prefix).map(str::to_string)
|
||||
}
|
||||
|
||||
pub(crate) fn inspect_omp_agent_config(
|
||||
paths: &[PathBuf],
|
||||
port: u16,
|
||||
api_key: &str,
|
||||
) -> Result<(bool, Option<String>), String> {
|
||||
if paths.len() != 2 || !paths[0].is_file() || !paths[1].is_file() {
|
||||
return Ok((false, None));
|
||||
}
|
||||
let models = read_agent_yaml_mapping_or_empty(&paths[0], "Oh My Pi models.yml")?;
|
||||
let settings = read_agent_yaml_mapping_or_empty(&paths[1], "Oh My Pi config.yml")?;
|
||||
let provider = omp_provider_mapping(&models);
|
||||
let model = omp_selected_model(&settings);
|
||||
let expected = format!("{}/v1", managed_core_loopback_origin(port));
|
||||
let configured = provider
|
||||
.and_then(|provider| yaml_mapping_value(provider, "baseUrl"))
|
||||
.and_then(serde_norway::Value::as_str)
|
||||
== Some(expected.as_str())
|
||||
&& provider
|
||||
.and_then(|provider| yaml_mapping_value(provider, "apiKey"))
|
||||
.and_then(serde_norway::Value::as_str)
|
||||
== Some(OMP_AGENT_API_KEY_ENV)
|
||||
&& provider
|
||||
.and_then(|provider| yaml_mapping_value(provider, "api"))
|
||||
.and_then(serde_norway::Value::as_str)
|
||||
.is_some_and(|api| api == OMP_AGENT_API || api == "openai-completions")
|
||||
&& provider
|
||||
.and_then(|provider| yaml_mapping_value(provider, "discovery"))
|
||||
.and_then(serde_norway::Value::as_mapping)
|
||||
.and_then(|discovery| yaml_mapping_value(discovery, "type"))
|
||||
.and_then(serde_norway::Value::as_str)
|
||||
== Some("openai-models-list")
|
||||
&& model.is_some();
|
||||
let _ = api_key;
|
||||
Ok((configured, model))
|
||||
}
|
||||
|
||||
pub(crate) fn omp_has_managed_marker(paths: &[PathBuf]) -> Result<bool, String> {
|
||||
if paths.first().is_none_or(|path| !path.is_file()) {
|
||||
return Ok(false);
|
||||
}
|
||||
let models = read_agent_yaml_mapping_or_empty(&paths[0], "Oh My Pi models.yml")?;
|
||||
Ok(omp_provider_mapping(&models).is_some())
|
||||
}
|
||||
|
||||
pub(crate) fn find_omp_executable(home: &Path) -> Option<PathBuf> {
|
||||
let bun = home.join(".bun/bin");
|
||||
#[cfg(target_os = "windows")]
|
||||
let managed = [bun.join("omp.exe"), bun.join("omp.cmd")];
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let managed = [bun.join("omp")];
|
||||
managed
|
||||
.into_iter()
|
||||
.find(|path| agent_cli_executable(path))
|
||||
.or_else(|| find_named_agent_executable(home, &["omp"]))
|
||||
}
|
||||
|
||||
pub(crate) fn inspect_kimi_code_agent_config(
|
||||
path: &Path,
|
||||
port: u16,
|
||||
|
||||
@@ -161,6 +161,7 @@ pub(crate) fn launch_agent(
|
||||
&launch_directory,
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
&terminal,
|
||||
);
|
||||
}
|
||||
@@ -188,7 +189,7 @@ pub(crate) fn launch_agent(
|
||||
}
|
||||
let executable = env::current_exe().map_err(|_| "Unable to locate CPA launch adapter")?;
|
||||
let directory = resolve_launch_directory(working_directory.as_deref(), &home)?;
|
||||
launch_cli_agent(&executable, client.name(), &directory, &antigravity_cli_helper_arguments(&home), &[], &terminal)
|
||||
launch_cli_agent(&executable, client.name(), &directory, &antigravity_cli_helper_arguments(&home), &[], &[], &terminal)
|
||||
}
|
||||
(AgentClient::WorkBuddy, "app") => {
|
||||
let executable = find_workbuddy_desktop_executable(&home)
|
||||
@@ -233,12 +234,14 @@ pub(crate) fn launch_agent(
|
||||
&[]
|
||||
};
|
||||
let arguments = agent_cli_launch_arguments(client, deepseek_harness_options.as_ref())?;
|
||||
let environment_to_set = omp_launch_environment(client, effective_agent_api_key(&config));
|
||||
launch_cli_agent(
|
||||
&executable,
|
||||
client.name(),
|
||||
&launch_directory,
|
||||
&arguments,
|
||||
environment_to_remove,
|
||||
&environment_to_set,
|
||||
&terminal,
|
||||
)
|
||||
}
|
||||
@@ -247,6 +250,15 @@ pub(crate) fn launch_agent(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn omp_launch_environment(client: AgentClient, api_key: &str) -> Vec<(&str, &str)> {
|
||||
if client == AgentClient::Omp {
|
||||
vec![(OMP_AGENT_API_KEY_ENV, api_key)]
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn deepseek_harness_launch_mode(
|
||||
options: Option<&DeepSeekHarnessLaunchOptions>,
|
||||
) -> Result<String, String> {
|
||||
@@ -1186,6 +1198,7 @@ fn launch_cli_agent(
|
||||
working_directory: &Path,
|
||||
arguments: &[String],
|
||||
environment_to_remove: &[&str],
|
||||
environment_to_set: &[(&str, &str)],
|
||||
terminal: &str,
|
||||
) -> Result<(), String> {
|
||||
let removals = environment_to_remove
|
||||
@@ -1193,6 +1206,11 @@ fn launch_cli_agent(
|
||||
.map(|key| format!("-u {}", shell_single_quote(key)))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
let assignments = environment_to_set
|
||||
.iter()
|
||||
.map(|(key, value)| format!("{}={}", shell_single_quote(key), shell_single_quote(value)))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
let invocation = std::iter::once(shell_single_quote(&path_to_string(executable)))
|
||||
.chain(
|
||||
arguments
|
||||
@@ -1202,9 +1220,10 @@ fn launch_cli_agent(
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
let command_line = format!(
|
||||
"cd {} && exec env {} {}",
|
||||
"cd {} && exec env {} {} {}",
|
||||
shell_single_quote(&path_to_string(working_directory)),
|
||||
removals,
|
||||
assignments,
|
||||
invocation,
|
||||
);
|
||||
let script = if terminal == "iterm2" {
|
||||
@@ -1245,6 +1264,7 @@ fn launch_cli_agent(
|
||||
working_directory: &Path,
|
||||
command_arguments: &[String],
|
||||
environment_to_remove: &[&str],
|
||||
environment_to_set: &[(&str, &str)],
|
||||
terminal: &str,
|
||||
) -> Result<(), String> {
|
||||
let definitions = linux_terminal_definitions();
|
||||
@@ -1269,6 +1289,9 @@ fn launch_cli_agent(
|
||||
for key in environment_to_remove {
|
||||
command.env_remove(key);
|
||||
}
|
||||
for (key, value) in environment_to_set {
|
||||
command.env(key, value);
|
||||
}
|
||||
match command.spawn() {
|
||||
Ok(_) => return Ok(()),
|
||||
Err(error) => last_error = Some(error.to_string()),
|
||||
@@ -1347,6 +1370,7 @@ fn launch_cli_agent(
|
||||
working_directory: &Path,
|
||||
arguments: &[String],
|
||||
environment_to_remove: &[&str],
|
||||
environment_to_set: &[(&str, &str)],
|
||||
terminal: &str,
|
||||
) -> Result<(), String> {
|
||||
use std::os::windows::process::CommandExt;
|
||||
@@ -1413,6 +1437,9 @@ fn launch_cli_agent(
|
||||
for key in environment_to_remove {
|
||||
command.env_remove(key);
|
||||
}
|
||||
for (key, value) in environment_to_set {
|
||||
command.env(key, value);
|
||||
}
|
||||
command
|
||||
.spawn()
|
||||
.map(|_| ())
|
||||
@@ -1426,6 +1453,7 @@ fn launch_cli_agent(
|
||||
_working_directory: &Path,
|
||||
_arguments: &[String],
|
||||
_environment_to_remove: &[&str],
|
||||
_environment_to_set: &[(&str, &str)],
|
||||
_terminal: &str,
|
||||
) -> Result<(), String> {
|
||||
Err(format!("The current platform does not support launching {label}"))
|
||||
|
||||
@@ -748,6 +748,10 @@ pub(crate) fn fresh_agent_contents_with_oauth(
|
||||
model,
|
||||
models,
|
||||
)?]),
|
||||
AgentClient::Omp => Ok(vec![
|
||||
build_omp_models_config(None, &openai_base, api_key)?,
|
||||
build_omp_settings_config(None, model)?,
|
||||
]),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -770,7 +774,7 @@ pub(crate) fn agent_contents_equal(client: AgentClient, actual: &str, expected:
|
||||
serde_yaml::from_str::<serde_yaml::Value>(actual).ok()
|
||||
== serde_yaml::from_str::<serde_yaml::Value>(expected).ok()
|
||||
}
|
||||
AgentClient::DeepSeekHarness => {
|
||||
AgentClient::Omp | AgentClient::DeepSeekHarness => {
|
||||
serde_norway::from_str::<serde_norway::Value>(actual).ok()
|
||||
== serde_norway::from_str::<serde_norway::Value>(expected).ok()
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ fn tracked_configuration_paths(app: &tauri::AppHandle) -> Result<Vec<PathBuf>, S
|
||||
AgentClient::AntigravityCli,
|
||||
AgentClient::KimiCode,
|
||||
AgentClient::GrokBuild,
|
||||
AgentClient::Omp,
|
||||
] {
|
||||
paths.extend(agent_managed_paths(client, &home));
|
||||
}
|
||||
|
||||
@@ -174,6 +174,11 @@ const DEEPSEEK_HARNESS_SETTINGS_FILE: &str = "settings.yaml";
|
||||
const DEEPSEEK_HARNESS_CREDENTIALS_FILE: &str = ".credentials.yaml";
|
||||
const PI_AGENT_ID: &str = "pi";
|
||||
const PI_AGENT_NAME: &str = "Pi";
|
||||
const OMP_AGENT_PROVIDER_ID: &str = "easy-cliproxyapi";
|
||||
const OMP_AGENT_API_KEY_ENV: &str = "EASYCLIPROXYAPI_API_KEY";
|
||||
const OMP_AGENT_API: &str = "openai-responses";
|
||||
const OMP_MODELS_FILE: &str = "models.yml";
|
||||
const OMP_SETTINGS_FILE: &str = "config.yml";
|
||||
const PI_CLIPROXYAPI_PACKAGE: &str = "npm:@router-for-me/pi-cliproxyapi-provider";
|
||||
const PI_CLIPROXYAPI_NPM_LATEST_URL: &str =
|
||||
"https://registry.npmjs.org/@router-for-me%2Fpi-cliproxyapi-provider/latest";
|
||||
@@ -1358,6 +1363,7 @@ enum AgentClient {
|
||||
AntigravityCli,
|
||||
KimiCode,
|
||||
GrokBuild,
|
||||
Omp,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -1382,6 +1388,7 @@ impl AgentClient {
|
||||
"antigravity-cli" => Ok(Self::AntigravityCli),
|
||||
"kimi-code" => Ok(Self::KimiCode),
|
||||
"grok-build" => Ok(Self::GrokBuild),
|
||||
"omp" => Ok(Self::Omp),
|
||||
_ => Err(format!("Unsupported agent client: {value}")),
|
||||
}
|
||||
}
|
||||
@@ -1400,6 +1407,7 @@ impl AgentClient {
|
||||
Self::AntigravityCli => "antigravity-cli",
|
||||
Self::KimiCode => "kimi-code",
|
||||
Self::GrokBuild => "grok-build",
|
||||
Self::Omp => "omp",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1417,6 +1425,7 @@ impl AgentClient {
|
||||
Self::AntigravityCli => "Antigravity CLI",
|
||||
Self::KimiCode => "Kimi Code",
|
||||
Self::GrokBuild => "Grok Build",
|
||||
Self::Omp => "Oh My Pi",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1443,6 +1452,7 @@ impl AgentClient {
|
||||
Self::AntigravityCli => &["agy"],
|
||||
Self::KimiCode => &["kimi"],
|
||||
Self::GrokBuild => &["grok"],
|
||||
Self::Omp => &["omp"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1549,6 +1549,53 @@ async fn codex_configuration_uses_public_v1_model_ids_for_selection_and_files()
|
||||
assert_eq!(ids, written);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn omp_configuration_discovers_models_without_storing_the_api_key() {
|
||||
let home = agent_test_home("omp-provider");
|
||||
let existing_models = "providers:\n other:\n baseUrl: https://example.test/v1\n apiKey: KEEP_OTHER\n";
|
||||
let existing_settings = "shellPath: E:/software/toolchain/w64devkit/bin/bash.exe\nmodelRoles:\n tiny: local/lfm2.5-230m\n";
|
||||
let models = build_omp_models_config(Some(existing_models), "http://127.0.0.1:8317/v1", "secret-value").unwrap();
|
||||
let settings = build_omp_settings_config(Some(existing_settings), "gpt-test").unwrap();
|
||||
let models_value: serde_norway::Value = serde_norway::from_str(&models).unwrap();
|
||||
let settings_value: serde_norway::Value = serde_norway::from_str(&settings).unwrap();
|
||||
let provider = &models_value["providers"]["easy-cliproxyapi"];
|
||||
assert_eq!(provider["baseUrl"].as_str(), Some("http://127.0.0.1:8317/v1"));
|
||||
assert_eq!(provider["apiKey"].as_str(), Some("EASYCLIPROXYAPI_API_KEY"));
|
||||
assert_eq!(provider["api"].as_str(), Some("openai-responses"));
|
||||
assert_eq!(provider["discovery"]["type"].as_str(), Some("openai-models-list"));
|
||||
assert!(provider.get("models").is_none());
|
||||
assert!(!models.contains("secret-value"));
|
||||
assert_eq!(models_value["providers"]["other"]["apiKey"].as_str(), Some("KEEP_OTHER"));
|
||||
assert_eq!(settings_value["shellPath"].as_str(), Some("E:/software/toolchain/w64devkit/bin/bash.exe"));
|
||||
assert_eq!(settings_value["modelRoles"]["tiny"].as_str(), Some("local/lfm2.5-230m"));
|
||||
assert_eq!(settings_value["modelRoles"]["default"].as_str(), Some("easy-cliproxyapi/gpt-test"));
|
||||
|
||||
let paths = agent_config_paths(AgentClient::Omp, &home);
|
||||
fs::create_dir_all(paths[0].parent().unwrap()).unwrap();
|
||||
fs::write(&paths[0], models).unwrap();
|
||||
fs::write(&paths[1], settings).unwrap();
|
||||
let (configured, model) = inspect_omp_agent_config(&paths, 8317, "secret-value").unwrap();
|
||||
assert!(configured);
|
||||
assert_eq!(model.as_deref(), Some("gpt-test"));
|
||||
assert!(omp_has_managed_marker(&paths).unwrap());
|
||||
|
||||
let removed = prepare_omp_managed_removal(&paths).unwrap();
|
||||
for (path, bytes) in removed {
|
||||
match bytes {
|
||||
Some(bytes) => fs::write(&path, bytes).unwrap(),
|
||||
None => fs::write(&path, "").unwrap(),
|
||||
}
|
||||
}
|
||||
let restored_models = fs::read_to_string(&paths[0]).unwrap();
|
||||
let restored_settings = fs::read_to_string(&paths[1]).unwrap();
|
||||
assert!(!restored_models.contains("easy-cliproxyapi"));
|
||||
assert!(restored_models.contains("KEEP_OTHER"));
|
||||
assert!(!restored_settings.contains("easy-cliproxyapi"));
|
||||
assert!(restored_settings.contains("shellPath"));
|
||||
assert!(restored_settings.contains("local/lfm2.5-230m"));
|
||||
fs::remove_dir_all(home).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kimi_grok_and_zcode_use_cpa_runtime_context_windows() {
|
||||
for client in [
|
||||
@@ -1566,6 +1613,7 @@ fn kimi_grok_and_zcode_use_cpa_runtime_context_windows() {
|
||||
AgentClient::OpenClaw,
|
||||
AgentClient::Hermes,
|
||||
AgentClient::DeepSeekHarness,
|
||||
AgentClient::Omp,
|
||||
] {
|
||||
assert!(!agent_uses_cpa_runtime_context_windows(client));
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ fn installation_detection_separates_executables_from_version_metadata() {
|
||||
AgentClient::DeepSeekHarness,
|
||||
AgentClient::KimiCode,
|
||||
AgentClient::GrokBuild,
|
||||
AgentClient::Omp,
|
||||
] {
|
||||
assert!(agent_installation_detected(client, None, true, false));
|
||||
assert!(!agent_installation_detected(client, None, false, false));
|
||||
|
||||
Reference in new issue
Block a user