use super::*;
pub(super) const CANVAS: Color = Color::Rgb(21, 25, 31);
pub(super) const ACCENT: Color = Color::Rgb(125, 182, 255);
pub(super) const TN_GREEN: Color = Color::Rgb(78, 201, 139);
pub(super) const TN_YELLOW: Color = Color::Rgb(215, 168, 75);
pub(super) const TN_RED: Color = Color::Rgb(224, 108, 117);
pub(super) const TN_CYAN: Color = Color::Rgb(110, 198, 217);
pub(super) const TN_ORANGE: Color = TN_YELLOW;
pub(super) const TN_PURPLE: Color = Color::Rgb(182, 155, 241);
pub(super) const TN_FG: Color = Color::Rgb(220, 220, 220);
pub(super) const TN_GRAY: Color = Color::Rgb(120, 123, 125);
pub(super) const TN_SUBTLE: Color = Color::Rgb(95, 99, 104);
pub(super) const BORDER_SUBTLE: Color = Color::Rgb(52, 58, 64);
pub(super) const SURFACE_SOFT: Color = Color::Rgb(27, 31, 37);
pub(super) const SURFACE_USER: Color = Color::Rgb(49, 53, 58);
pub(super) const SURFACE_SELECTED: Color = Color::Rgb(42, 46, 52);
pub(super) const BRAND_GRADIENT: [Color; 8] = [
Color::Rgb(86, 156, 255),
Color::Rgb(70, 214, 255),
Color::Rgb(76, 230, 190),
Color::Rgb(249, 211, 92),
Color::Rgb(255, 139, 92),
Color::Rgb(255, 101, 155),
Color::Rgb(190, 124, 255),
Color::Rgb(116, 133, 255),
];
pub(super) const ULTRACODE_ANIMATION_TICK: Duration = Duration::from_millis(60);
pub(super) const ULTRACODE_CONFIRM_ANIMATION: Duration = Duration::from_millis(1_140);
pub(super) const ULTRACODE_BORDER_ANIMATION: Duration = Duration::from_millis(2_520);
pub(super) fn agent_chrome_theme() -> TuiTheme {
TuiTheme {
primary: ACCENT,
secondary: TN_CYAN,
bg: CANVAS,
fg: TN_FG,
muted: TN_GRAY,
border: BORDER_SUBTLE,
success: TN_GREEN,
warning: TN_ORANGE,
error: TN_RED,
info: TN_CYAN,
surface: SURFACE_SOFT,
highlight: SURFACE_SELECTED,
}
}
pub(super) fn agent_chrome(theme: &TuiTheme) -> AgentChrome<'_> {
AgentChrome::new(theme)
}
pub(super) fn os_platform_guide(base_url: &str) -> String {
format!(
"[OS platform] You are signed in to the OS open platform at {base_url} (via /login). \
DEFAULT RULE: while signed in, \"OS\" in the user's questions ALWAYS means THIS OS platform — \
never this machine's operating system. So \"what's my OS account\", \"what modules does OS have\", \
etc. are about the platform. Answer them via the platform's progressive API; do NOT answer from \
this machine (whoami / hostname / paths / working directory describe the local box, not the \
platform — they are the WRONG answer). The endpoint and auth token are ALREADY in your shell \
environment (exported at login) — use them directly; do NOT read ~/.a3s/os-auth.json or any config \
file on each call:\n\
curl -s -X POST \"$A3S_OS_BASE_URL/api/v1/kernel/capabilities\" \
-H \"Authorization: Bearer $A3S_OS_TOKEN\" -H 'Content-Type: application/json' \
-d '{{\"action\":\"list\"}}'\n\
Body fields: `action` = list|search|describe|execute, plus `module` / `operation` / `params`. \
Go broad→narrow: `list` (modules) → `describe`/`search` for the one operation → `execute`. \
For `list`/`search`/`describe`, pipe through `jq` to extract only the fields you need so output \
stays a few lines (e.g. `| jq -r '.data.modules[].name'`). \
For `execute`, ALWAYS add `\"shaped\":true` to the request body — that is what makes the response \
carry the `.view` popup deep-link — and do NOT jq-narrow an execute response: pipe it whole (it is \
already compact), so `.view` survives. If you strip `.view` (or omit `\"shaped\":true`), the user \
loses the Open view button. \
Summarize the result for the user in a few lines; do NOT paste the whole raw JSON back. \
After your summary, ALWAYS output the trace on its own line, exactly \
`↳ requestId <requestId> · <timestamp>`. \
You do NOT print the view link yourself: whenever the execute output carries a `.view`, the host \
automatically shows a one-click `Open view` button that opens the authenticated progressive UI popup \
(the user's OS login is injected, no re-login). Never print the raw URL. The \
`a3s-os-capabilities` skill has full examples."
)
}
pub(super) const SLASH_COMMANDS: &[(&str, &str)] = &[
(
"/model",
"switch model (←/→ for Claude/GPT accounts if signed in)",
),
("/init", "analyze the project and generate AGENTS.md"),
("/config", "edit config.acl in the built-in editor"),
("/theme", "cycle the code-highlight theme (Codex Dark …)"),
(
"/flow",
"select a workflow asset → OS Workflow as a Service designer (needs /login) · /flow <text> drafts one",
),
(
"/agent",
"select an agent definition → local dev · Agent as a Service or Function as a Service by kind",
),
(
"/mcp",
"select an MCP server asset → local dev · publish/run/test via OS Function as a Service",
),
(
"/skill",
"select a skill asset → local dev · publish/deploy via OS Function as a Service",
),
(
"/okf",
"select an OKF package → local dev · publish/deploy via OS Knowledge service",
),
("/login", "sign in to the configured OS account"),
("/logout", "sign out from the configured OS account"),
("/plugin", "enable/disable Claude skills & plugins"),
("/reload", "re-scan skills/plugins (hot-reload the / menu)"),
("/update", "upgrade a3s to the latest release"),
("/ide", "superfile-style file browser + editor"),
(
"/memory",
"browse memory as an event/entity graph with tiers and forget candidates",
),
(
"/research",
"inspect DeepResearch event state · status, explain, or strict replay",
),
(
"/kb",
"open the local personal knowledge base · add/import/search/vault",
),
(
"/ctx",
"search past sessions (ctx) · /ctx <n> attach · /ctx save <n> keep as memory",
),
("/effort", "adjust model effort (low … ultracode)"),
("/compact", "summarize + compact the conversation context"),
("/goal", "run a durable Ultracode goal until verified"),
(
"/loop",
"engineered loop dashboard · agent-aware in /agent mode · /loop <task> quick loop",
),
(
"/sleep",
"consolidate today's work into memory (experience · preferences · knowledge)",
),
("/help", "show commands and shortcuts"),
(
"/fork",
"branch a new session from this point (original kept)",
),
("/clear", "reset the conversation"),
("/auto", "switch to auto-approve mode"),
("/exit", "quit a3s code"),
];
pub(super) const IDLE_ONLY: &[&str] = &[
"/clear", "/compact", "/model", "/effort", "/goal", "/loop", "/reload", "/update", "/init",
"/fork", "/sleep", "/flow", "/agent", "/mcp", "/skill", "/okf", "/kb",
];
pub(super) fn slash_candidates(input: &str) -> Vec<(&'static str, &'static str)> {
SLASH_COMMANDS
.iter()
.filter(|(cmd, _)| cmd.starts_with(input))
.copied()
.collect()
}
pub(super) fn slash_tail<'a>(input: &'a str, command: &str) -> Option<&'a str> {
input
.strip_prefix(command)
.filter(|rest| rest.is_empty() || rest.starts_with(char::is_whitespace))
}
pub(super) fn os_asset_category_query(category: &str, query: &str) -> String {
let query = query.trim();
if query.is_empty() {
format!("category:{category}")
} else {
format!("category:{category} {query}")
}
}
pub(super) fn runtime_asset_query(category: &str, asset_hint: &str, query: &str) -> String {
let category = category.trim();
let asset_hint = asset_hint.trim();
let query = query.trim();
let mut parts = Vec::new();
if !category.is_empty() {
parts.push(format!("category:{category}"));
}
if !asset_hint.is_empty() {
parts.push(asset_hint.to_string());
}
if !query.is_empty() {
parts.push(query.to_string());
}
parts.join(" ")
}
pub(super) fn cancel_pending_picker<Panel, Pending>(
picker: &mut Option<Panel>,
pending: &mut Option<Pending>,
) {
*picker = None;
*pending = None;
}
pub(super) fn os_required_message(cmd: &str, os_configured: bool) -> String {
if os_configured {
format!(" {cmd} needs OS — sign in with /login first")
} else {
format!(
" {cmd} needs OS — configure `os = \"https://your-os-host\"` in config.acl, then /login"
)
}
}
pub(super) fn os_required_alert(cmd: &str, os_configured: bool) -> String {
let body = os_required_message(cmd, os_configured)
.trim_start()
.to_string();
format!(
" {}",
Alert::new(AlertKind::Warning, body).color(TN_YELLOW).view()
)
}
pub(super) fn ide_flash_line(kind: ToastKind, message: impl Into<String>) -> String {
let color = match kind {
ToastKind::Info => TN_CYAN,
ToastKind::Success => TN_GREEN,
ToastKind::Warning => TN_YELLOW,
ToastKind::Error => TN_RED,
};
Toast::new(kind, message).color(color).view()
}
pub(super) fn needs_synthesis(
synthesis_inflight: bool,
synthesis_used: bool,
had_agent_activity: bool,
text_after_activity: bool,
) -> bool {
!synthesis_inflight && !synthesis_used && had_agent_activity && !text_after_activity
}
pub(super) fn estimate_tokens(s: &str) -> usize {
let (ascii, wide) = s.chars().fold((0usize, 0usize), |(a, w), c| {
if c.is_ascii() {
(a + 1, w)
} else {
(a, w + 1)
}
});
ascii / 4 + wide
}
pub(super) fn ctx_limit_for_model(
model_ctx: &std::collections::HashMap<String, u32>,
model: &str,
) -> u32 {
let codex_model = model
.strip_prefix("codex/")
.or_else(|| model.strip_prefix("openai-codex/"))
.unwrap_or(model);
context_limit_for_model(
model,
model_ctx.get(model).copied(),
crate::account_providers::codex::codex_model_context(codex_model),
)
}
#[derive(Clone)]
pub(super) enum LlmOverride {
Static(Arc<dyn a3s_code_core::llm::LlmClient>),
Codex(crate::account_providers::codex::CodexClient),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct CodexEffortStatus {
pub(super) effective: String,
pub(super) capped: bool,
}
impl LlmOverride {
pub(super) fn client_for_effort(
&self,
a3s_effort: &str,
) -> Arc<dyn a3s_code_core::llm::LlmClient> {
match self {
Self::Static(client) => client.clone(),
Self::Codex(client) => Arc::new(client.with_a3s_effort(a3s_effort)),
}
}
pub(super) fn codex_effort_status(&self, a3s_effort: &str) -> Option<CodexEffortStatus> {
let Self::Codex(client) = self else {
return None;
};
let requested =
crate::account_providers::codex::native_reasoning_effort_for_a3s(a3s_effort)?;
let effective = client.resolve_reasoning_effort(a3s_effort)?;
Some(CodexEffortStatus {
capped: effective != requested,
effective,
})
}
}
pub(super) fn os_gateway_llm_override(
session: &crate::a3s_os::StoredOsSession,
model: &str,
) -> LlmOverride {
let origin = crate::a3s_os::os_origin(&session.address);
LlmOverride::Static(Arc::new(
a3s_code_core::llm::OpenAiClient::new(session.access_token.clone(), model.to_string())
.with_base_url(origin)
.with_chat_completions_path("/api/v1/llm/chat/completions")
.with_provider_name("OS Gateway"),
))
}
pub(super) fn restore_model_selection(
models: &[String],
os_session: Option<&crate::a3s_os::StoredOsSession>,
session_id: &str,
) -> Option<(String, Option<LlmOverride>)> {
let preference = load_model_selection_preference()?;
match preference.source {
ModelSelectionSource::Config => models
.iter()
.any(|model| model == &preference.model)
.then_some((preference.model, None)),
ModelSelectionSource::Claude | ModelSelectionSource::CodeBuddy => {
let provider = preference.source.account_provider()?;
if !provider.is_available() {
return None;
}
let model = provider.canonical_model(&preference.model);
let client = provider.client(&model, session_id).ok()?;
Some((model, Some(LlmOverride::Static(client))))
}
ModelSelectionSource::Codex => {
if !crate::account_providers::AccountProvider::Codex.is_available() {
return None;
}
let effort = load_tui_effort_preference().unwrap_or(DEFAULT_TUI_EFFORT_INDEX);
let client =
crate::account_providers::codex::CodexClient::from_codex_login_with_effort(
&preference.model,
session_id,
EFFORT_LEVELS[effort].id,
)
.ok()?;
Some((preference.model, Some(LlmOverride::Codex(client))))
}
ModelSelectionSource::OsGateway => {
let session = os_session?;
let client = os_gateway_llm_override(session, &preference.model);
Some((preference.model, Some(client)))
}
}
}
pub(super) fn apply_launch_model_options(
opts: SessionOptions,
model: Option<&str>,
llm_override: Option<&LlmOverride>,
effort: &str,
code_config: &CodeConfig,
session_id: &str,
) -> SessionOptions {
let opts = match model {
Some(model) => opts.with_model(model),
None => opts,
};
match llm_override {
Some(client) => opts.with_llm_client(client.client_for_effort(effort)),
None => match crate::session_llm::resolve_config_llm_client(code_config, &opts, session_id)
{
Ok(client) => opts.with_llm_client(client),
Err(_) => opts,
},
}
}
pub(super) fn ctx_warn_tier(pct: usize, warned: u8) -> (u8, Option<u8>) {
let tier: u8 = if pct >= 85 {
85
} else if pct >= 70 {
70
} else {
0
};
(tier, (tier > warned).then_some(tier))
}