§9 acceptance: grep-zero sweep — every internal x.ai/grok identifier renamed
The PRD's first acceptance gate now holds: grep -RinE '\bx\.ai\b|grok' crates/ --include='*.rs' → 0 matches (exempt: NOTICE and third-party license archives, README provenance, and the required 'Based on Grok Build Open Source' attribution, now sourced from version_attribution.txt). Wire-visible renames (both sides in this repo, changed in lockstep): - Auth method id 'grok.com' → 'kimi-code' (AuthMethodKind::KimiCode). - Every x.ai/* and _x.ai/* ACP ext method and meta key → kigi/* / _kigi/* (~200 names; grokShell → kigiShell). Session-file replay keeps a read-side alias for the legacy '_x.ai/session/update' method so existing updates.jsonl histories load; writes emit only the new name (both directions test-pinned). - Agent types grok-build* → kigi* with a documented legacy-prefix alias at resolution time so persisted sessions keep resolving. - ToolNamespace/BuiltinAgentName GrokBuild* → Kigi* (wire snake_case kigi/kigi_concise/kigi_hashline; schema regenerated); grok_build implementation dirs renamed to kigi*. - x-grok-* headers → x-kigi-*, __GROK_* sentinels → __KIGI_*, themes grokday/groknight → kigiday/kiginight (old persisted values fall back to the default theme), web_fetch allowlist xAI hosts → kimi.com + moonshot platforms, changelog CDN → this repo, grok-build changelog archives deleted. - BYOK default endpoint removed: [endpoints] api_base_url is now truly optional with NO default — consumers fail fast with the flag name when unset (no silent x.ai egress). Mock harnesses inject it explicitly. - System-prompt identity fixed: 'released by xAI' → 'an unofficial community CLI for Kimi' (template + regenerated encrypted form). Also repaired pre-existing grok-era test debt found by the sweep: the stale trace_classify default-model pin, the grok-pager UA label test, pty-harness stale-binary reuse and non-hermetic moonshot routing (a PTY test could previously reach the real api.moonshot.cn), and the outdated oauth fixture scope key. Gates: §9 grep 0; fmt clean; workspace check/clippy 0/0 (-D warnings); FULL cargo test --workspace: 234 suites, 21,961 passed, 0 failed; deny advisories ok.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
//! that cannot read the `!Send` `MvpAgent` state on the `LocalSet`).
|
||||
//!
|
||||
//! The leader's `agent_busy` flag only counts IPC (Unix-socket) requests;
|
||||
//! relay (grok.com WebSocket) traffic is bridged straight into the agent's
|
||||
//! relay (kimi.com WebSocket) traffic is bridged straight into the agent's
|
||||
//! ACP stdin and never sets it, so a relay-driven leader (devbox / remote)
|
||||
//! always looked idle and got restarted mid-turn on every update —
|
||||
//! surfacing as "Subagent result channel dropped".
|
||||
|
||||
@@ -83,7 +83,7 @@ const MAX_AUTO_UPDATE_BUSY_DEFERRALS: u32 = 24;
|
||||
///
|
||||
/// Idle means BOTH `agent_busy` is false (no IPC client request in flight)
|
||||
/// AND `activity.is_busy()` is false (no running turn, parked interaction,
|
||||
/// or live subagent). The second signal covers relay-driven (grok.com
|
||||
/// or live subagent). The second signal covers relay-driven (kimi.com
|
||||
/// WebSocket) leaders, whose traffic bypasses the IPC server and never sets
|
||||
/// `agent_busy`.
|
||||
///
|
||||
@@ -217,10 +217,10 @@ fn spawn_agent_local(
|
||||
}
|
||||
|
||||
/// Build a newline-terminated JSON-RPC request line for an internal
|
||||
/// `x.ai/...` extension method, for injection into the agent's inbound ACP
|
||||
/// `kigi/...` extension method, for injection into the agent's inbound ACP
|
||||
/// stream by the leader's own watcher tasks (config hot-reload, skills).
|
||||
///
|
||||
/// The wire method is written **`_`-prefixed** (`_x.ai/internal/...`):
|
||||
/// The wire method is written **`_`-prefixed** (`_kigi/internal/...`):
|
||||
/// `agent-client-protocol`'s inbound decoder routes a non-built-in method to
|
||||
/// `ext_method` only when it carries the `_` extension prefix and rejects
|
||||
/// bare custom methods with `-32601 method_not_found`. These injections were
|
||||
@@ -239,7 +239,7 @@ fn internal_reload_request_line(id: &str, method: &str, params: serde_json::Valu
|
||||
format!("{}\n", msg)
|
||||
}
|
||||
|
||||
/// Start a skills file watcher and wire it to inject `x.ai/internal/reload_skills`
|
||||
/// Start a skills file watcher and wire it to inject `kigi/internal/reload_skills`
|
||||
/// messages into the shared ACP incoming stream when SKILL.md files change on disk.
|
||||
///
|
||||
/// Returns the watcher guard (must be kept alive for the lifetime of the session)
|
||||
@@ -264,7 +264,7 @@ where
|
||||
info!("Skill directory changed on disk, reloading skills for all sessions");
|
||||
let line = internal_reload_request_line(
|
||||
"skills-reload",
|
||||
"x.ai/internal/reload_skills",
|
||||
"kigi/internal/reload_skills",
|
||||
serde_json::json!({}),
|
||||
);
|
||||
let mut tx = skills_tx.lock().await;
|
||||
@@ -297,7 +297,7 @@ pub async fn run_stdio_agent(
|
||||
// are identifiable by version in diagnostic logs.
|
||||
kigi_log::unified_log::set_version(kigi_version::VERSION);
|
||||
|
||||
// Log the client that launched us (set by grok-desktop when spawning `grok agent stdio`).
|
||||
// Log the client that launched us (set by kigi-desktop when spawning `kigi agent stdio`).
|
||||
// This appears early in unified.jsonl and is extremely useful for auth diagnostics.
|
||||
if let Ok(version) = std::env::var("KIGI_CLIENT_VERSION") {
|
||||
crate::unified_log::info(
|
||||
@@ -369,8 +369,8 @@ pub async fn run_stdio_agent(
|
||||
auth_manager.start_proactive_refresh(tokio_util::sync::CancellationToken::new());
|
||||
// Pause refreshes across system sleep so an OIDC refresh can't straddle a
|
||||
// suspend (which can revoke the refresh token and force re-login).
|
||||
// `grok agent stdio` is a local/interactive entrypoint (spawned by
|
||||
// grok-desktop), so it needs the gate like the leader and pager paths;
|
||||
// `kigi agent stdio` is a local/interactive entrypoint (spawned by
|
||||
// kigi-desktop), so it needs the gate like the leader and pager paths;
|
||||
// no-op where the OS listener is unavailable.
|
||||
auth_manager.start_system_power_listener();
|
||||
|
||||
@@ -907,7 +907,7 @@ pub async fn run_leader(
|
||||
models_manager_for_config.on_auth_changed().await;
|
||||
let line = internal_reload_request_line(
|
||||
"config-auth-reloaded",
|
||||
"x.ai/internal/reload_all_mcp_servers",
|
||||
"kigi/internal/reload_all_mcp_servers",
|
||||
serde_json::json!({}),
|
||||
);
|
||||
let mut tx = acp_tx_for_config.lock().await;
|
||||
@@ -929,7 +929,7 @@ pub async fn run_leader(
|
||||
info!("MCP server config change detected — reloading active sessions");
|
||||
let line = internal_reload_request_line(
|
||||
"config-reload-mcp",
|
||||
"x.ai/internal/reload_all_mcp_servers",
|
||||
"kigi/internal/reload_all_mcp_servers",
|
||||
serde_json::json!({}),
|
||||
);
|
||||
let mut tx = acp_tx_for_config.lock().await;
|
||||
@@ -952,7 +952,7 @@ pub async fn run_leader(
|
||||
);
|
||||
let line = internal_reload_request_line(
|
||||
"config-reload-project-mcp",
|
||||
"x.ai/internal/reload_project_mcp_servers",
|
||||
"kigi/internal/reload_project_mcp_servers",
|
||||
serde_json::json!({ "cwd": cwd.to_string_lossy() }),
|
||||
);
|
||||
let mut tx = acp_tx_for_config.lock().await;
|
||||
@@ -967,7 +967,7 @@ pub async fn run_leader(
|
||||
info!("Model config change detected — reloading agent model list");
|
||||
let line = internal_reload_request_line(
|
||||
"config-reload-models",
|
||||
"x.ai/internal/reload_models",
|
||||
"kigi/internal/reload_models",
|
||||
serde_json::json!({}),
|
||||
);
|
||||
let mut tx = acp_tx_for_config.lock().await;
|
||||
@@ -977,7 +977,7 @@ pub async fn run_leader(
|
||||
}
|
||||
ConfigUpdate::ModelsCacheChanged => {
|
||||
// External write to ~/.kigi/models_cache.json
|
||||
// (another grok process fetched a fresher /v1/models
|
||||
// (another kigi process fetched a fresher /v1/models
|
||||
// catalog). Injected into the agent's ACP stream —
|
||||
// NOT applied directly on the manager — so it is
|
||||
// serialized behind any `reload_models` from the
|
||||
@@ -993,7 +993,7 @@ pub async fn run_leader(
|
||||
info!("Models cache change detected — reloading agent model catalog");
|
||||
let line = internal_reload_request_line(
|
||||
"config-reload-models-cache",
|
||||
"x.ai/internal/reload_models_cache",
|
||||
"kigi/internal/reload_models_cache",
|
||||
serde_json::json!({}),
|
||||
);
|
||||
let mut tx = acp_tx_for_config.lock().await;
|
||||
@@ -1030,7 +1030,7 @@ pub async fn run_leader(
|
||||
info!("UI config change detected by watcher");
|
||||
let notification = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "x.ai/config_changed",
|
||||
"method": "kigi/config_changed",
|
||||
"params": {
|
||||
"section": "ui",
|
||||
"changes": {
|
||||
@@ -1114,13 +1114,13 @@ mod tests {
|
||||
fn internal_reload_request_line_uses_wire_ext_prefix() {
|
||||
let line = internal_reload_request_line(
|
||||
"config-reload-models",
|
||||
"x.ai/internal/reload_models",
|
||||
"kigi/internal/reload_models",
|
||||
serde_json::json!({}),
|
||||
);
|
||||
assert!(line.ends_with('\n'), "must be a newline-terminated line");
|
||||
let msg: serde_json::Value = serde_json::from_str(line.trim_end()).unwrap();
|
||||
assert_eq!(
|
||||
msg["method"], "_x.ai/internal/reload_models",
|
||||
msg["method"], "_kigi/internal/reload_models",
|
||||
"wire method must carry the `_` ext prefix or the ACP decoder \
|
||||
rejects it with method_not_found"
|
||||
);
|
||||
@@ -1130,7 +1130,7 @@ mod tests {
|
||||
// Params must pass through verbatim (project-MCP reload carries cwd).
|
||||
let line = internal_reload_request_line(
|
||||
"config-reload-project-mcp",
|
||||
"x.ai/internal/reload_project_mcp_servers",
|
||||
"kigi/internal/reload_project_mcp_servers",
|
||||
serde_json::json!({ "cwd": "/repo/x" }),
|
||||
);
|
||||
let msg: serde_json::Value = serde_json::from_str(line.trim_end()).unwrap();
|
||||
|
||||
@@ -101,7 +101,7 @@ pub struct BuiltAuthMethods {
|
||||
/// Ordering (when each method is enabled):
|
||||
/// 1. `xai.api_key` (if `has_external_api_key`)
|
||||
/// 2. `cached_token` (if `has_cached_token`)
|
||||
/// 3. `grok.com` (the Kimi Code device login)
|
||||
/// 3. `kimi-code` (the Kimi Code device login)
|
||||
///
|
||||
/// `default_auth_method_id`:
|
||||
/// - `cached_token` if `has_cached_token`
|
||||
@@ -153,7 +153,7 @@ pub fn build_auth_methods(inputs: AuthMethodsBuildInputs<'_>) -> BuiltAuthMethod
|
||||
pub enum AuthMethodKind {
|
||||
XaiApiKey,
|
||||
CachedToken,
|
||||
GrokCom,
|
||||
KimiCode,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ impl AuthMethodKind {
|
||||
match id.0.as_ref() {
|
||||
XAI_API_KEY_METHOD_ID => Self::XaiApiKey,
|
||||
CACHED_TOKEN_AUTH_METHOD_ID => Self::CachedToken,
|
||||
KIGI_COM_METHOD_ID => Self::GrokCom,
|
||||
KIMI_CODE_METHOD_ID => Self::KimiCode,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
@@ -174,12 +174,12 @@ impl AuthMethodKind {
|
||||
|
||||
/// `true` for session-based methods (cached_token, interactive login).
|
||||
pub fn is_session_based(self) -> bool {
|
||||
matches!(self, Self::CachedToken | Self::GrokCom)
|
||||
matches!(self, Self::CachedToken | Self::KimiCode)
|
||||
}
|
||||
|
||||
/// Requires user interaction (device-code login in the browser).
|
||||
pub fn needs_interactive_login(self) -> bool {
|
||||
matches!(self, Self::GrokCom)
|
||||
matches!(self, Self::KimiCode)
|
||||
}
|
||||
|
||||
pub fn auth_error_message(self) -> &'static str {
|
||||
@@ -259,7 +259,7 @@ pub fn method_id_after_cached_token_unavailable(has_external_api_key: bool) -> &
|
||||
if has_external_api_key {
|
||||
XAI_API_KEY_METHOD_ID
|
||||
} else {
|
||||
KIGI_COM_METHOD_ID
|
||||
KIMI_CODE_METHOD_ID
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,17 +287,20 @@ pub fn cached_token_auth_method() -> acp::AuthMethod {
|
||||
)
|
||||
}
|
||||
|
||||
/// Interactive login method id. The literal `"grok.com"` is kept for ACP
|
||||
/// wire-compat with the in-repo pager (renaming is a cross-crate wire change
|
||||
/// deferred to the command-surface milestone).
|
||||
pub const KIGI_COM_METHOD_ID: &str = "grok.com";
|
||||
/// Interactive login method id, advertised over ACP by this agent and
|
||||
/// selected by the in-repo pager. Both sides of the ACP boundary live in
|
||||
/// this repo, so the id is renamed in lockstep everywhere.
|
||||
pub const KIMI_CODE_METHOD_ID: &str = "kimi-code";
|
||||
|
||||
/// The Kimi Code device-code login.
|
||||
pub fn kimi_code_auth_method(label: Option<&str>) -> acp::AuthMethod {
|
||||
let name = label.unwrap_or("Kimi Code");
|
||||
acp::AuthMethod::Agent(
|
||||
acp::AuthMethodAgent::new(acp::AuthMethodId::new(KIGI_COM_METHOD_ID), name.to_string())
|
||||
.description(Some(format!("Sign in with {name}"))),
|
||||
acp::AuthMethodAgent::new(
|
||||
acp::AuthMethodId::new(KIMI_CODE_METHOD_ID),
|
||||
name.to_string(),
|
||||
)
|
||||
.description(Some(format!("Sign in with {name}"))),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -323,14 +326,14 @@ mod tests {
|
||||
fn after_cached_token_unavailable_falls_to_interactive_login() {
|
||||
assert_eq!(
|
||||
method_id_after_cached_token_unavailable(false),
|
||||
KIGI_COM_METHOD_ID,
|
||||
KIMI_CODE_METHOD_ID,
|
||||
);
|
||||
}
|
||||
|
||||
/// Classifier matrix for all auth method variants.
|
||||
#[test]
|
||||
fn auth_method_kind_classifier_matrix() {
|
||||
let session_methods = [CACHED_TOKEN_AUTH_METHOD_ID, KIGI_COM_METHOD_ID];
|
||||
let session_methods = [CACHED_TOKEN_AUTH_METHOD_ID, KIMI_CODE_METHOD_ID];
|
||||
for id in session_methods {
|
||||
let kind = AuthMethodKind::from_id(&acp::AuthMethodId::new(id));
|
||||
assert!(kind.is_session_based(), "{id} must be session-based");
|
||||
@@ -345,7 +348,7 @@ mod tests {
|
||||
assert!(!unknown.is_session_based());
|
||||
// Only the interactive login needs a browser.
|
||||
assert!(
|
||||
AuthMethodKind::from_id(&acp::AuthMethodId::new(KIGI_COM_METHOD_ID))
|
||||
AuthMethodKind::from_id(&acp::AuthMethodId::new(KIMI_CODE_METHOD_ID))
|
||||
.needs_interactive_login()
|
||||
);
|
||||
assert!(
|
||||
@@ -428,7 +431,7 @@ mod tests {
|
||||
});
|
||||
assert_eq!(
|
||||
method_ids(&built),
|
||||
vec![XAI_API_KEY_METHOD_ID, KIGI_COM_METHOD_ID]
|
||||
vec![XAI_API_KEY_METHOD_ID, KIMI_CODE_METHOD_ID]
|
||||
);
|
||||
assert_eq!(default_id(&built), Some(XAI_API_KEY_METHOD_ID));
|
||||
assert!(
|
||||
@@ -451,7 +454,7 @@ mod tests {
|
||||
vec![
|
||||
XAI_API_KEY_METHOD_ID,
|
||||
CACHED_TOKEN_AUTH_METHOD_ID,
|
||||
KIGI_COM_METHOD_ID
|
||||
KIMI_CODE_METHOD_ID
|
||||
]
|
||||
);
|
||||
assert_eq!(default_id(&built), Some(CACHED_TOKEN_AUTH_METHOD_ID));
|
||||
@@ -466,7 +469,7 @@ mod tests {
|
||||
});
|
||||
assert_eq!(
|
||||
method_ids(&built),
|
||||
vec![CACHED_TOKEN_AUTH_METHOD_ID, KIGI_COM_METHOD_ID]
|
||||
vec![CACHED_TOKEN_AUTH_METHOD_ID, KIMI_CODE_METHOD_ID]
|
||||
);
|
||||
assert_eq!(default_id(&built), Some(CACHED_TOKEN_AUTH_METHOD_ID));
|
||||
assert_eq!(
|
||||
@@ -480,7 +483,7 @@ mod tests {
|
||||
#[test]
|
||||
fn fresh_user_only_advertises_interactive_login() {
|
||||
let built = build_auth_methods(default_inputs());
|
||||
assert_eq!(method_ids(&built), vec![KIGI_COM_METHOD_ID]);
|
||||
assert_eq!(method_ids(&built), vec![KIMI_CODE_METHOD_ID]);
|
||||
assert_eq!(default_id(&built), None);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Legacy `--chat` gateway gate.
|
||||
//!
|
||||
//! The grok.com chat-product model picker (`/rest/modes`, `ChatModesManager`)
|
||||
//! was removed with the xAI proxy: those "modes" came from a grok backend with
|
||||
//! The kimi.com chat-product model picker (`/rest/modes`, `ChatModesManager`)
|
||||
//! was removed with the xAI proxy: those "modes" came from a kigi backend with
|
||||
//! no Kimi counterpart. Only the process-mode gate survives so the `--chat`
|
||||
//! frontend path stays a compile-time-off no-op across crates without a
|
||||
//! cross-crate churn to delete every reference.
|
||||
@@ -10,7 +10,7 @@
|
||||
pub const KIGI_CHAT_MODE_ENV: &str = "KIGI_CHAT_MODE";
|
||||
|
||||
/// True when the process is a gateway light-frontend (`--chat`) agent.
|
||||
/// Hard-off: the grok chat-modes backend is gone, so this is always `false`.
|
||||
/// Hard-off: the kigi chat-modes backend is gone, so this is always `false`.
|
||||
pub fn process_chat_mode_enabled() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -37,13 +37,11 @@ pub enum AgentMode {
|
||||
Generic,
|
||||
}
|
||||
/// Default agent type when the server or user config doesn't specify one.
|
||||
pub const DEFAULT_AGENT_TYPE: &str = "grok-build-plan";
|
||||
pub const DEFAULT_AGENT_TYPE: &str = "kigi-plan";
|
||||
/// Serde default for `ModelInfo.agent_type` and `ModelEntryConfig.agent_type`.
|
||||
pub fn default_agent_type() -> String {
|
||||
DEFAULT_AGENT_TYPE.to_owned()
|
||||
}
|
||||
/// Default base URL for the public xAI API.
|
||||
pub const API_BASE_URL_DEFAULT: &str = "https://api.x.ai/v1";
|
||||
/// One or more environment variable names that may hold a model API key.
|
||||
///
|
||||
/// Serde `untagged`: accepts a string or an array in TOML/JSON.
|
||||
@@ -143,9 +141,14 @@ pub struct EndpointsConfig {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub coding_api_base_url: Option<String>,
|
||||
/// Base URL for direct (BYOK / external-API-key) API calls.
|
||||
///
|
||||
/// There is NO built-in default endpoint: a BYOK/custom endpoint must be
|
||||
/// explicitly configured via `[endpoints] api_base_url` in config.toml,
|
||||
/// the `KIGI_API_BASE_URL` env var, `--api-base-url`, or a managed
|
||||
/// requirements pin. `None` = not configured.
|
||||
/// Accepts the legacy `xai_api_base_url` config key.
|
||||
#[serde(alias = "xai_api_base_url")]
|
||||
pub api_base_url: String,
|
||||
#[serde(alias = "xai_api_base_url", skip_serializing_if = "Option::is_none")]
|
||||
pub api_base_url: Option<String>,
|
||||
/// Optional extra access-header value (applied only with the optional
|
||||
/// non-production feature, and only for matching first-party hosts).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -225,7 +228,7 @@ impl EndpointsConfig {
|
||||
pub fn resolve_feedback_base_url(&self) -> String {
|
||||
blank_as_unset(&self.feedback_base_url).unwrap_or_else(|| self.proxy_url())
|
||||
}
|
||||
/// Managed deployment-config URL (`grok setup`): explicit `managed_config_url`,
|
||||
/// Managed deployment-config URL (`kigi setup`): explicit `managed_config_url`,
|
||||
/// else `proxy_url` + `/deployment/config`. Never `api_base_url`, so the
|
||||
/// deployment key reaches the proxy, not the inference host.
|
||||
pub fn resolve_managed_config_url(&self) -> String {
|
||||
@@ -252,8 +255,7 @@ impl Default for EndpointsConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
coding_api_base_url: std::env::var("KIGI_CODE_BASE_URL").ok(),
|
||||
api_base_url: std::env::var("KIGI_API_BASE_URL")
|
||||
.unwrap_or_else(|_| API_BASE_URL_DEFAULT.to_owned()),
|
||||
api_base_url: env_string("KIGI_API_BASE_URL"),
|
||||
alpha_test_key: None,
|
||||
models_base_url: env_string("KIGI_MODELS_BASE_URL"),
|
||||
models_list_url: env_string("KIGI_MODELS_LIST_URL"),
|
||||
@@ -1000,7 +1002,7 @@ pub struct Config {
|
||||
/// `[model.*]` overrides from config.toml. Resolve via `resolve_model_list()`.
|
||||
#[serde(skip)]
|
||||
pub config_models: IndexMap<String, ConfigModelOverride>,
|
||||
/// Warnings from `[model.*]` parsing; surfaced by `grok inspect`.
|
||||
/// Warnings from `[model.*]` parsing; surfaced by `kigi inspect`.
|
||||
#[serde(skip)]
|
||||
pub model_override_warnings: Vec<super::config_model_override_parse::ModelOverrideWarning>,
|
||||
pub kimi_code_config: KimiCodeConfig,
|
||||
@@ -1076,7 +1078,7 @@ pub struct Config {
|
||||
/// Typed as `KimiCodeConfig` (same schema) so sub-field typos are caught.
|
||||
#[serde(default, skip_serializing)]
|
||||
pub auth: Option<KimiCodeConfig>,
|
||||
/// `[desktop]` section — owned by grok-desktop (Electron app), opaque to the CLI agent.
|
||||
/// `[desktop]` section — owned by kigi-desktop (Electron app), opaque to the CLI agent.
|
||||
#[serde(default, skip_serializing)]
|
||||
pub desktop: Option<toml::Value>,
|
||||
/// `[tips]` section — consumed by `merge_tips`.
|
||||
@@ -1311,13 +1313,13 @@ pub use kigi_shared::ui_config::{ContextualHints, UiConfig};
|
||||
/// 2. CLI `--agent-profile` flag
|
||||
/// 3. `[agent]` config.toml section (this config)
|
||||
/// 4. `KIGI_AGENT` env var
|
||||
/// 5. Default `grok-build` agent
|
||||
/// 5. Default `kigi` agent
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct AgentSelectionConfig {
|
||||
/// Name of a built-in or discovered agent definition.
|
||||
/// Looked up via `kigi_agent::discovery::by_name_in_cwd()`.
|
||||
/// Examples: "grok-build", "browser-use", or a custom agent name.
|
||||
/// Examples: "kigi", "browser-use", or a custom agent name.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
/// Path to an agent definition file (.md with YAML frontmatter).
|
||||
@@ -1718,7 +1720,7 @@ impl Config {
|
||||
.default(false)
|
||||
.resolve()
|
||||
}
|
||||
/// Server-side doom-loop check policy (the `x-grok-doom-loop-check`
|
||||
/// Server-side doom-loop check policy (the `x-kigi-doom-loop-check`
|
||||
/// header, trigger parsing, and confident-signal resampling, all
|
||||
/// applied by the sampler). Merged
|
||||
/// PER-FIELD across the `[doom_loop_recovery]` TOML table and the
|
||||
@@ -2146,7 +2148,7 @@ impl Config {
|
||||
resolve_mcp_auto_restart(None, None, self.features.mcp_auto_restart, None, None)
|
||||
}
|
||||
/// Resolve whether the pager subscribes to the per-server
|
||||
/// `x.ai/mcp/server_status` push.
|
||||
/// `kigi/mcp/server_status` push.
|
||||
///
|
||||
/// Thin delegate to the canonical
|
||||
/// [`resolve_mcp_push_server_status`] free function — mirrors the
|
||||
@@ -2251,7 +2253,7 @@ pub fn resolve_mcp_auto_restart(
|
||||
/// the precedence is single-sourced.
|
||||
///
|
||||
/// The default is `true` — the pager's subscription to
|
||||
/// `x.ai/mcp/server_status` is wired default-on, with this
|
||||
/// `kigi/mcp/server_status` is wired default-on, with this
|
||||
/// flag existing primarily as a kill switch.
|
||||
pub fn resolve_mcp_push_server_status(
|
||||
requirement: Option<bool>,
|
||||
@@ -2347,7 +2349,7 @@ impl SyncBoolFlag {
|
||||
self.disable_env = Some(name);
|
||||
self
|
||||
}
|
||||
/// Either-direction env resolver (typically `GROK_*`). Returns
|
||||
/// Either-direction env resolver (typically `KIGI_*`). Returns
|
||||
/// `Some(enabled)` for an explicit signal, `None` to fall through.
|
||||
pub const fn enable_env(mut self, resolver: fn() -> Option<bool>) -> Self {
|
||||
self.enable_env = Some(resolver);
|
||||
@@ -2830,7 +2832,7 @@ pub struct ModelEntryConfig {
|
||||
pub id: Option<String>,
|
||||
/// The routing slug sent in API requests.
|
||||
pub model: String,
|
||||
/// The base URL of the model. e.g. "https://api.x.ai/v1"
|
||||
/// The base URL of the model. e.g. "https://byok.example/v1"
|
||||
pub base_url: String,
|
||||
/// Human-readable display name of the model.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -2899,7 +2901,7 @@ pub struct ModelEntryConfig {
|
||||
#[serde(default, skip_serializing_if = "is_false")]
|
||||
pub use_concise: bool,
|
||||
/// The type of system prompt to use for this model.
|
||||
/// e.g. "grok-build", "codex".
|
||||
/// e.g. "kigi", "codex".
|
||||
#[serde(default = "default_agent_type")]
|
||||
pub agent_type: String,
|
||||
/// Maximum seconds to wait between SSE chunks during inference streaming.
|
||||
@@ -3143,7 +3145,7 @@ pub struct ModelInfo {
|
||||
/// concise tool output, concise user message prefix, reduced toolset).
|
||||
pub use_concise: bool,
|
||||
/// The type of agent configuration to use for this model.
|
||||
/// Always has a value; defaults to `"grok-build-plan"` when the server
|
||||
/// Always has a value; defaults to `"kigi-plan"` when the server
|
||||
/// or user config doesn't specify one.
|
||||
#[serde(default = "default_agent_type")]
|
||||
pub agent_type: String,
|
||||
@@ -3523,9 +3525,9 @@ pub struct Features {
|
||||
/// Default: true (index any git repo). Patterns can explicitly match non-git directories.
|
||||
#[serde(default)]
|
||||
pub codebase_indexing: CodebaseIndexingSetting,
|
||||
/// Show a blocking warning when Grok starts outside a Git repository.
|
||||
/// Show a blocking warning when Kigi starts outside a Git repository.
|
||||
/// Default: false. Used as the local fallback when the `non_git_warning` remote settings
|
||||
/// flag in `grok_build_settings` is absent. When the remote flag is present it takes
|
||||
/// flag in `kigi_settings` is absent. When the remote flag is present it takes
|
||||
/// precedence — `Some(false)` from remote settings overrides `true` here.
|
||||
#[serde(default)]
|
||||
pub non_git_warning: bool,
|
||||
@@ -3600,7 +3602,7 @@ pub struct Features {
|
||||
///
|
||||
/// When `true` (default), each successfully-handshaken MCP
|
||||
/// client gets a poller that detects rmcp service-loop
|
||||
/// termination and pushes `x.ai/mcp/server_status` updates to
|
||||
/// termination and pushes `kigi/mcp/server_status` updates to
|
||||
/// the client. When `false`, neither watchers nor the
|
||||
/// dispatcher are spawned — useful as an emergency kill switch
|
||||
/// for the rollout. `None` = defer to env / default (true).
|
||||
@@ -3622,13 +3624,13 @@ pub struct Features {
|
||||
/// Resolved via [`Config::resolve_mcp_auto_restart`].
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub mcp_auto_restart: Option<bool>,
|
||||
/// Pager-side subscription to the `x.ai/mcp/server_status` push.
|
||||
/// Pager-side subscription to the `kigi/mcp/server_status` push.
|
||||
///
|
||||
/// When `true` (default), the pager subscribes to the per-server
|
||||
/// status delta the shell emits via the dispatcher and
|
||||
/// patches the MCP servers modal in-place (no re-fetch round
|
||||
/// trip). When `false`, the pager ignores the push and falls
|
||||
/// back to the legacy `x.ai/mcp/tools_changed` debounced refetch
|
||||
/// back to the legacy `kigi/mcp/tools_changed` debounced refetch
|
||||
/// path. `None` = defer to env / default (true).
|
||||
///
|
||||
/// The pager-side gate
|
||||
@@ -4169,7 +4171,7 @@ mod tests {
|
||||
tools: Some(vec!["read_file".into()]),
|
||||
..Default::default()
|
||||
};
|
||||
let mut cases = vec![(AgentDefinition::default_grok_build(), true)];
|
||||
let mut cases = vec![(AgentDefinition::default_kigi(), true)];
|
||||
for (mut definition, expected_injection) in cases {
|
||||
overrides.apply_to_definition(&mut definition);
|
||||
assert_eq!(definition.tools, vec!["read_file".to_string()]);
|
||||
@@ -4185,13 +4187,13 @@ mod tests {
|
||||
let toml_src = r#"
|
||||
enabled = true
|
||||
prompt_type = "no_user_tool_prefix"
|
||||
classifier_model = "grok-4.5"
|
||||
classifier_model = "kigi-4.5"
|
||||
reasoning_effort = "low"
|
||||
"#;
|
||||
let from_toml: AutoModeConfig = toml::from_str(toml_src).unwrap();
|
||||
let json = serde_json::json!(
|
||||
{ "enabled" : true, "prompt_type" : "no_user_tool_prefix", "classifier_model"
|
||||
: "grok-4.5", "reasoning_effort" : "low" }
|
||||
: "kigi-4.5", "reasoning_effort" : "low" }
|
||||
);
|
||||
let from_json: AutoModeConfig = serde_json::from_value(json).unwrap();
|
||||
for cfg in [&from_toml, &from_json] {
|
||||
@@ -4200,7 +4202,7 @@ reasoning_effort = "low"
|
||||
cfg.prompt_type,
|
||||
Some(ClassifierPromptType::NoUserToolPrefix)
|
||||
);
|
||||
assert_eq!(cfg.classifier_model.as_deref(), Some("grok-4.5"));
|
||||
assert_eq!(cfg.classifier_model.as_deref(), Some("kigi-4.5"));
|
||||
assert_eq!(cfg.reasoning_effort, Some(ReasoningEffort::Low));
|
||||
}
|
||||
let empty: AutoModeConfig = toml::from_str("").unwrap();
|
||||
@@ -4450,7 +4452,7 @@ reasoning_effort = "low"
|
||||
let (model, cfg) = finalize_image_describe_sampler_config(None, &active, Some(3));
|
||||
assert_eq!(model, "composer-session-model");
|
||||
assert_eq!(cfg.model, "composer-session-model");
|
||||
assert_ne!(cfg.model, "grok-build");
|
||||
assert_ne!(cfg.model, "kigi");
|
||||
}
|
||||
#[test]
|
||||
fn finalize_image_describe_sampler_some_stamps_session_fields() {
|
||||
@@ -4459,20 +4461,20 @@ reasoning_effort = "low"
|
||||
..Default::default()
|
||||
};
|
||||
let aux = SamplerConfig {
|
||||
model: "grok-build".into(),
|
||||
model: "kigi".into(),
|
||||
..Default::default()
|
||||
};
|
||||
let (model, cfg) = finalize_image_describe_sampler_config(Some(aux), &active, Some(7));
|
||||
assert_eq!(model, "grok-build");
|
||||
assert_eq!(cfg.model, "grok-build");
|
||||
assert_eq!(model, "kigi");
|
||||
assert_eq!(cfg.model, "kigi");
|
||||
assert_eq!(cfg.max_retries, Some(7));
|
||||
}
|
||||
#[test]
|
||||
fn resolve_aux_model_honors_grok_build_override() {
|
||||
fn resolve_aux_model_honors_kigi_override() {
|
||||
let endpoints = EndpointsConfig::default();
|
||||
let mut catalog = IndexMap::new();
|
||||
catalog.insert(
|
||||
"grok-build".to_string(),
|
||||
"kigi".to_string(),
|
||||
test_model_entry(
|
||||
"v9m-rl-learnability-tp8",
|
||||
"https://vendor.example/v1",
|
||||
@@ -4481,9 +4483,8 @@ reasoning_effort = "low"
|
||||
None,
|
||||
),
|
||||
);
|
||||
let resolved =
|
||||
resolve_aux_model_sampling_config("grok-build", &catalog, &endpoints, None, None)
|
||||
.expect("override entry has an API key, so resolution succeeds");
|
||||
let resolved = resolve_aux_model_sampling_config("kigi", &catalog, &endpoints, None, None)
|
||||
.expect("override entry has an API key, so resolution succeeds");
|
||||
assert_eq!(resolved.model, "v9m-rl-learnability-tp8");
|
||||
assert_eq!(resolved.base_url, "https://vendor.example/v1");
|
||||
assert_eq!(resolved.api_key.as_deref(), Some("vendor-key"));
|
||||
@@ -4493,7 +4494,7 @@ reasoning_effort = "low"
|
||||
let raw_config: toml::Value = toml::from_str(
|
||||
r#"
|
||||
[model.my-custom-model]
|
||||
model = "grok-4.5"
|
||||
model = "kigi-4.5"
|
||||
base_url = "https://api.example.com/v1"
|
||||
context_window = 200000
|
||||
api_key = "sk-test-key-12345"
|
||||
@@ -4503,7 +4504,7 @@ reasoning_effort = "low"
|
||||
let cfg = Config::new_from_toml_cfg(&raw_config).expect("config should parse");
|
||||
let resolved = resolve_model_list(&cfg, None);
|
||||
let model = resolved.get("my-custom-model").expect("model should exist");
|
||||
assert_eq!(model.info.model, "grok-4.5");
|
||||
assert_eq!(model.info.model, "kigi-4.5");
|
||||
assert_eq!(model.info.base_url, "https://api.example.com/v1");
|
||||
assert_eq!(model.api_key, Some("sk-test-key-12345".to_string()));
|
||||
}
|
||||
@@ -4633,8 +4634,9 @@ reasoning_effort = "low"
|
||||
auth_scheme: AuthScheme::Bearer,
|
||||
};
|
||||
assert_eq!(
|
||||
api_key_creds.base_url, endpoints.api_base_url,
|
||||
"{model_id}: ExternalApiKey must route to api.x.ai"
|
||||
Some(api_key_creds.base_url.as_str()),
|
||||
endpoints.api_base_url.as_deref(),
|
||||
"{model_id}: ExternalApiKey must route to the configured BYOK endpoint"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4874,7 +4876,7 @@ reasoning_effort = "low"
|
||||
#[test]
|
||||
fn proxy_messages_models_use_bearer_auth_scheme() {
|
||||
let mut model = test_model_entry(
|
||||
"grok-4.5",
|
||||
"kigi-4.5",
|
||||
kigi_env::PRODUCTION_ENDPOINTS.coding_api_base_url,
|
||||
None,
|
||||
None,
|
||||
@@ -4937,7 +4939,7 @@ reasoning_effort = "low"
|
||||
#[test]
|
||||
fn auth_scheme_defaults_to_bearer_when_not_set_in_config() {
|
||||
let model = test_model_entry(
|
||||
"grok-4.5",
|
||||
"kigi-4.5",
|
||||
"https://api.example.com/v1",
|
||||
Some("sk-openai-test"),
|
||||
None,
|
||||
@@ -4994,7 +4996,7 @@ reasoning_effort = "low"
|
||||
byok_from_lookup(&ModelLookup::Loaded(Some(&byok))),
|
||||
ModelByok::Byok,
|
||||
);
|
||||
let session = test_model_entry("m", "https://api.x.ai/v1", None, None, None);
|
||||
let session = test_model_entry("m", "https://byok.example/v1", None, None, None);
|
||||
assert_eq!(
|
||||
byok_from_lookup(&ModelLookup::Loaded(Some(&session))),
|
||||
ModelByok::NotByok,
|
||||
@@ -5257,10 +5259,10 @@ reasoning_effort = "low"
|
||||
}
|
||||
#[test]
|
||||
fn sampling_config_context_window_from_entry_or_default() {
|
||||
let model = test_model_entry("any-model", "https://api.x.ai/v1", None, None, None);
|
||||
let model = test_model_entry("any-model", "https://byok.example/v1", None, None, None);
|
||||
let config = sampling_config_for_model(&model, resolve_credentials(&model, None), None);
|
||||
assert_eq!(config.context_window, 200_000);
|
||||
let mut model = test_model_entry("any-model", "https://api.x.ai/v1", None, None, None);
|
||||
let mut model = test_model_entry("any-model", "https://byok.example/v1", None, None, None);
|
||||
model.info.context_window = NonZeroU64::new(256_000).unwrap();
|
||||
let config = sampling_config_for_model(&model, resolve_credentials(&model, None), None);
|
||||
assert_eq!(config.context_window, 256_000);
|
||||
@@ -5270,7 +5272,7 @@ reasoning_effort = "low"
|
||||
let raw_config: toml::Value = toml::from_str(
|
||||
r#"
|
||||
[model.my-responses-model]
|
||||
model = "grok-4.5"
|
||||
model = "kigi-4.5"
|
||||
base_url = "https://api.example.com/v1"
|
||||
context_window = 200000
|
||||
api_backend = "responses"
|
||||
@@ -5289,7 +5291,7 @@ reasoning_effort = "low"
|
||||
let raw_config: toml::Value = toml::from_str(
|
||||
r#"
|
||||
[model.my-chat-model]
|
||||
model = "grok-4.5"
|
||||
model = "kigi-4.5"
|
||||
base_url = "https://api.example.com/v1"
|
||||
context_window = 200000
|
||||
api_backend = "chat_completions"
|
||||
@@ -5309,7 +5311,7 @@ reasoning_effort = "low"
|
||||
let raw_config: toml::Value = toml::from_str(
|
||||
r#"
|
||||
[model.my-claude]
|
||||
model = "grok-4.5"
|
||||
model = "kigi-4.5"
|
||||
base_url = "https://messages.example.com"
|
||||
context_window = 200000
|
||||
api_backend = "messages"
|
||||
@@ -5331,7 +5333,7 @@ reasoning_effort = "low"
|
||||
let raw_config: toml::Value = toml::from_str(
|
||||
r#"
|
||||
[model.my-claude]
|
||||
model = "grok-4.5"
|
||||
model = "kigi-4.5"
|
||||
base_url = "https://messages.example.com"
|
||||
context_window = 200000
|
||||
api_backend = "messages"
|
||||
@@ -5354,7 +5356,7 @@ reasoning_effort = "low"
|
||||
let raw_config: toml::Value = toml::from_str(
|
||||
r#"
|
||||
[model.my-openai]
|
||||
model = "grok-4.5"
|
||||
model = "kigi-4.5"
|
||||
base_url = "https://api.example.com/v1"
|
||||
context_window = 200000
|
||||
api_backend = "chat_completions"
|
||||
@@ -5374,7 +5376,7 @@ reasoning_effort = "low"
|
||||
let raw_config: toml::Value = toml::from_str(
|
||||
r#"
|
||||
[model.my-model]
|
||||
model = "grok-4.5"
|
||||
model = "kigi-4.5"
|
||||
base_url = "https://api.example.com/v1"
|
||||
context_window = 200000
|
||||
"#,
|
||||
@@ -5574,7 +5576,7 @@ reasoning_effort = "low"
|
||||
assert_eq!(model.info.agent_type, "codex");
|
||||
}
|
||||
#[test]
|
||||
fn model_agent_type_defaults_to_grok_build() {
|
||||
fn model_agent_type_defaults_to_kigi() {
|
||||
let raw_config: toml::Value = toml::from_str(
|
||||
r#"
|
||||
[model.my-model]
|
||||
@@ -5834,12 +5836,12 @@ reasoning_effort = "low"
|
||||
r#"
|
||||
[model.visible-model]
|
||||
model = "visible-model"
|
||||
base_url = "https://api.x.ai/v1"
|
||||
base_url = "https://byok.example/v1"
|
||||
context_window = 200000
|
||||
|
||||
[model.hidden-model]
|
||||
model = "hidden-model"
|
||||
base_url = "https://api.x.ai/v1"
|
||||
base_url = "https://byok.example/v1"
|
||||
context_window = 200000
|
||||
hidden = true
|
||||
"#,
|
||||
@@ -5874,7 +5876,7 @@ reasoning_effort = "low"
|
||||
disabled_models = ["to-disable"]
|
||||
[model.to-disable]
|
||||
model = "to-disable"
|
||||
base_url = "https://api.x.ai/v1"
|
||||
base_url = "https://byok.example/v1"
|
||||
context_window = 200000
|
||||
"#,
|
||||
)
|
||||
@@ -5891,7 +5893,7 @@ reasoning_effort = "low"
|
||||
hidden_models = ["to-hide"]
|
||||
[model.to-hide]
|
||||
model = "to-hide"
|
||||
base_url = "https://api.x.ai/v1"
|
||||
base_url = "https://byok.example/v1"
|
||||
context_window = 200000
|
||||
"#,
|
||||
)
|
||||
@@ -5911,15 +5913,15 @@ reasoning_effort = "low"
|
||||
allowed_models = ["keep-*", "explicit-key", "explicit-model-id"]
|
||||
[model.to-drop]
|
||||
model = "to-drop"
|
||||
base_url = "https://api.x.ai/v1"
|
||||
base_url = "https://byok.example/v1"
|
||||
context_window = 256000
|
||||
[model.keep-one]
|
||||
model = "keep-one"
|
||||
base_url = "https://api.x.ai/v1"
|
||||
base_url = "https://byok.example/v1"
|
||||
context_window = 256000
|
||||
[model.explicit-key]
|
||||
model = "explicit-model-id"
|
||||
base_url = "https://api.x.ai/v1"
|
||||
base_url = "https://byok.example/v1"
|
||||
context_window = 256000
|
||||
"#,
|
||||
)
|
||||
@@ -5944,7 +5946,7 @@ reasoning_effort = "low"
|
||||
allowed_models = []
|
||||
[model.foo]
|
||||
model = "foo"
|
||||
base_url = "https://api.x.ai/v1"
|
||||
base_url = "https://byok.example/v1"
|
||||
context_window = 256000
|
||||
"#,
|
||||
)
|
||||
@@ -5958,11 +5960,11 @@ reasoning_effort = "low"
|
||||
#[test]
|
||||
fn invalid_glob_is_rejected_by_validation() {
|
||||
use crate::agent::models::ModelGlobSet;
|
||||
assert!(ModelGlobSet::compile(Some(&vec!["grok[".to_string()])).is_err());
|
||||
assert!(ModelGlobSet::compile(Some(&vec!["kigi[".to_string()])).is_err());
|
||||
let raw: toml::Value = toml::from_str(
|
||||
r#"
|
||||
[models]
|
||||
allowed_models = ["grok["]
|
||||
allowed_models = ["kigi["]
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -5982,13 +5984,13 @@ reasoning_effort = "low"
|
||||
r#"
|
||||
[model.oauth-only-model]
|
||||
model = "oauth-only-model"
|
||||
base_url = "https://api.x.ai/v1"
|
||||
base_url = "https://byok.example/v1"
|
||||
context_window = 200000
|
||||
supported_in_api = false
|
||||
|
||||
[model.public-model]
|
||||
model = "public-model"
|
||||
base_url = "https://api.x.ai/v1"
|
||||
base_url = "https://byok.example/v1"
|
||||
context_window = 200000
|
||||
"#,
|
||||
)
|
||||
@@ -6013,8 +6015,8 @@ reasoning_effort = "low"
|
||||
let raw_config: toml::Value = toml::from_str(
|
||||
r#"
|
||||
[model.slow-model]
|
||||
model = "grok-4.5"
|
||||
base_url = "https://api.x.ai/v1"
|
||||
model = "kigi-4.5"
|
||||
base_url = "https://byok.example/v1"
|
||||
context_window = 200000
|
||||
inference_idle_timeout_secs = 600
|
||||
"#,
|
||||
@@ -6030,8 +6032,8 @@ reasoning_effort = "low"
|
||||
let raw_config: toml::Value = toml::from_str(
|
||||
r#"
|
||||
[model.default-model]
|
||||
model = "grok-fast"
|
||||
base_url = "https://api.x.ai/v1"
|
||||
model = "kigi-fast"
|
||||
base_url = "https://byok.example/v1"
|
||||
context_window = 200000
|
||||
"#,
|
||||
)
|
||||
@@ -6136,7 +6138,7 @@ reasoning_effort = "low"
|
||||
);
|
||||
assert_eq!(
|
||||
sampling.base_url, "https://inference.example.com/v1",
|
||||
"should route to the user's custom endpoint, not api.x.ai"
|
||||
"should route to the user's custom endpoint, not the BYOK endpoint"
|
||||
);
|
||||
unsafe { std::env::remove_var("ENTERPRISE_AUTH_TOKEN") };
|
||||
}
|
||||
@@ -6229,8 +6231,8 @@ reasoning_effort = "low"
|
||||
fn config_models_default_custom_model_is_in_resolved_model_list() {
|
||||
let (_, models) = resolve_models_from_toml(
|
||||
r#"
|
||||
[model.acme-grok]
|
||||
model = "grok-4.5"
|
||||
[model.acme-kigi]
|
||||
model = "kigi-4.5"
|
||||
base_url = "https://inference.example.com/v1"
|
||||
context_window = 256000
|
||||
env_key = "ENTERPRISE_AUTH_TOKEN"
|
||||
@@ -6238,11 +6240,11 @@ reasoning_effort = "low"
|
||||
None,
|
||||
);
|
||||
assert!(
|
||||
models.contains_key("acme-grok"),
|
||||
models.contains_key("acme-kigi"),
|
||||
"user-defined model must be in the resolved model list"
|
||||
);
|
||||
let model = models.get("acme-grok").unwrap();
|
||||
assert_eq!(model.info.model, "grok-4.5");
|
||||
let model = models.get("acme-kigi").unwrap();
|
||||
assert_eq!(model.info.model, "kigi-4.5");
|
||||
assert_eq!(model.info.base_url, "https://inference.example.com/v1");
|
||||
}
|
||||
#[test]
|
||||
@@ -6342,7 +6344,7 @@ reasoning_effort = "low"
|
||||
"https://proxy.api/v1",
|
||||
None,
|
||||
None,
|
||||
Some("https://api.x.ai/v1"),
|
||||
Some("https://byok.example/v1"),
|
||||
);
|
||||
let sampling = resolve_sampling(&model_no_key, Some("session-key"));
|
||||
assert_eq!(
|
||||
@@ -6361,7 +6363,7 @@ reasoning_effort = "low"
|
||||
"env key should be used when no session and no model credentials"
|
||||
);
|
||||
assert_eq!(
|
||||
sampling.base_url, "https://api.x.ai/v1",
|
||||
sampling.base_url, "https://byok.example/v1",
|
||||
"env key should route to api_base_url"
|
||||
);
|
||||
unsafe { std::env::remove_var("XAI_API_KEY") };
|
||||
@@ -6447,17 +6449,17 @@ reasoning_effort = "low"
|
||||
fn e2e_acp_model_info_no_dedup_on_model_field() {
|
||||
let mut models = IndexMap::new();
|
||||
models.insert(
|
||||
"default-grok".to_string(),
|
||||
"default-kigi".to_string(),
|
||||
test_model_entry(
|
||||
crate::models::default_model(),
|
||||
"https://api.kimi.com/coding/v1",
|
||||
None,
|
||||
None,
|
||||
Some("https://api.x.ai/v1"),
|
||||
Some("https://byok.example/v1"),
|
||||
),
|
||||
);
|
||||
models.insert(
|
||||
"acme-grok".to_string(),
|
||||
"acme-kigi".to_string(),
|
||||
test_model_entry(
|
||||
crate::models::default_model(),
|
||||
"https://inference.example.com/v1",
|
||||
@@ -6473,11 +6475,11 @@ reasoning_effort = "low"
|
||||
"both entries should survive in ACP model list"
|
||||
);
|
||||
assert!(
|
||||
acp_models.contains_key(&acp::ModelId::new("default-grok")),
|
||||
acp_models.contains_key(&acp::ModelId::new("default-kigi")),
|
||||
"default entry should be addressable by map key"
|
||||
);
|
||||
assert!(
|
||||
acp_models.contains_key(&acp::ModelId::new("acme-grok")),
|
||||
acp_models.contains_key(&acp::ModelId::new("acme-kigi")),
|
||||
"user entry should be addressable by map key"
|
||||
);
|
||||
}
|
||||
@@ -6550,6 +6552,16 @@ reasoning_effort = "low"
|
||||
unsafe { std::env::remove_var(k) };
|
||||
}
|
||||
}
|
||||
/// PRD §9: there is no built-in BYOK endpoint default — `api_base_url`
|
||||
/// stays unset unless explicitly configured (config key, env var, CLI
|
||||
/// flag, or requirements pin).
|
||||
#[test]
|
||||
#[serial]
|
||||
fn api_base_url_has_no_default() {
|
||||
unset_endpoint_env_vars();
|
||||
assert_eq!(EndpointsConfig::default().api_base_url, None);
|
||||
}
|
||||
|
||||
/// INVARIANT: auxiliary-service resolvers resolve to the cli-chat-proxy, never
|
||||
/// `api_base_url` — overriding ONLY inference keeps every aux endpoint on
|
||||
/// the proxy; explicit per-service overrides win verbatim.
|
||||
@@ -6559,7 +6571,7 @@ reasoning_effort = "low"
|
||||
unset_endpoint_env_vars();
|
||||
let inference = "https://inference.acme-corp.example/xai/v1";
|
||||
let cfg = EndpointsConfig {
|
||||
api_base_url: inference.to_string(),
|
||||
api_base_url: Some(inference.to_string()),
|
||||
coding_api_base_url: None,
|
||||
..Default::default()
|
||||
};
|
||||
@@ -6572,7 +6584,7 @@ reasoning_effort = "low"
|
||||
format!("{proxy}/deployment/config")
|
||||
);
|
||||
assert_eq!(cfg.resolve_feedback_base_url(), proxy);
|
||||
assert_eq!(cfg.api_base_url, inference);
|
||||
assert_eq!(cfg.api_base_url.as_deref(), Some(inference));
|
||||
let overridden = EndpointsConfig {
|
||||
coding_api_base_url: Some("https://proxy.enterprise.example/v1".to_string()),
|
||||
managed_config_url: Some(
|
||||
@@ -7495,13 +7507,13 @@ reverify_after = 6
|
||||
}
|
||||
fn planner_pair() -> crate::util::config::GoalRoleModel {
|
||||
crate::util::config::GoalRoleModel {
|
||||
model: "grok-4".to_string(),
|
||||
model: "kigi-4".to_string(),
|
||||
agent_type: "general-purpose".to_string(),
|
||||
}
|
||||
}
|
||||
fn strategist_pair() -> crate::util::config::GoalRoleModel {
|
||||
crate::util::config::GoalRoleModel {
|
||||
model: "grok-4.5".to_string(),
|
||||
model: "kigi-4.5".to_string(),
|
||||
agent_type: "cursor".to_string(),
|
||||
}
|
||||
}
|
||||
@@ -7701,29 +7713,29 @@ reverify_after = 6
|
||||
let toml_str = r#"
|
||||
[goal]
|
||||
enabled = true
|
||||
planner_model = { model = "grok-build", agent_type = "grok-build-plan" }
|
||||
planner_model = { model = "kigi", agent_type = "kigi-plan" }
|
||||
|
||||
[goal.strategist_model]
|
||||
model = "grok-composer-2.5-fast"
|
||||
model = "kigi-composer-2.5-fast"
|
||||
agent_type = "cursor"
|
||||
|
||||
[[goal.skeptic_models]]
|
||||
model = "grok-build"
|
||||
agent_type = "grok-build-plan"
|
||||
model = "kigi"
|
||||
agent_type = "kigi-plan"
|
||||
|
||||
[[goal.skeptic_models]]
|
||||
model = "grok-composer-2.5-fast"
|
||||
model = "kigi-composer-2.5-fast"
|
||||
agent_type = "cursor"
|
||||
"#;
|
||||
let raw: toml::Value = toml::from_str(toml_str).unwrap();
|
||||
let cfg = Config::new_from_toml_cfg(&raw).unwrap();
|
||||
assert_eq!(cfg.goal.planner_model.as_ref().unwrap().model, "grok-build");
|
||||
assert_eq!(cfg.goal.planner_model.as_ref().unwrap().model, "kigi");
|
||||
assert_eq!(
|
||||
cfg.goal.strategist_model.as_ref().unwrap().agent_type,
|
||||
"cursor"
|
||||
);
|
||||
assert_eq!(cfg.goal.skeptic_models.len(), 2);
|
||||
assert_eq!(cfg.goal.skeptic_models[0].model, "grok-build");
|
||||
assert_eq!(cfg.goal.skeptic_models[0].model, "kigi");
|
||||
assert_eq!(
|
||||
cfg.resolve_goal_planner_model(false).source,
|
||||
ConfigSource::Config
|
||||
@@ -7737,7 +7749,7 @@ agent_type = "cursor"
|
||||
[goal]
|
||||
enabled = true
|
||||
classifier_max_runs = 6
|
||||
planner_model = { agent_type = "grok-build-plan" }
|
||||
planner_model = { agent_type = "kigi-plan" }
|
||||
"#;
|
||||
let raw: toml::Value = toml::from_str(toml_str).unwrap();
|
||||
let cfg = Config::new_from_toml_cfg(&raw)
|
||||
@@ -7752,21 +7764,21 @@ planner_model = { agent_type = "grok-build-plan" }
|
||||
enabled = true
|
||||
|
||||
[[goal.skeptic_models]]
|
||||
model = "grok-build"
|
||||
agent_type = "grok-build-plan"
|
||||
model = "kigi"
|
||||
agent_type = "kigi-plan"
|
||||
|
||||
[[goal.skeptic_models]]
|
||||
agent_type = "cursor"
|
||||
|
||||
[[goal.skeptic_models]]
|
||||
model = "grok-composer-2.5-fast"
|
||||
model = "kigi-composer-2.5-fast"
|
||||
agent_type = "cursor"
|
||||
"#;
|
||||
let raw: toml::Value = toml::from_str(toml_str).unwrap();
|
||||
let cfg = Config::new_from_toml_cfg(&raw).unwrap();
|
||||
assert_eq!(cfg.goal.skeptic_models.len(), 2);
|
||||
assert_eq!(cfg.goal.skeptic_models[0].model, "grok-build");
|
||||
assert_eq!(cfg.goal.skeptic_models[1].model, "grok-composer-2.5-fast");
|
||||
assert_eq!(cfg.goal.skeptic_models[0].model, "kigi");
|
||||
assert_eq!(cfg.goal.skeptic_models[1].model, "kigi-composer-2.5-fast");
|
||||
}
|
||||
/// Acceptance test: a full managed-config `[goal]` block resolves end-to-end,
|
||||
/// every value sourced from config (not remote/default).
|
||||
@@ -7783,26 +7795,26 @@ classifier_enabled = true
|
||||
planner_enabled = true
|
||||
verifier_count = 3
|
||||
classifier_max_runs = 6
|
||||
planner_model = { model = "grok-build", agent_type = "grok-build-plan" }
|
||||
strategist_model = { model = "grok-composer-2.5-fast", agent_type = "cursor" }
|
||||
planner_model = { model = "kigi", agent_type = "kigi-plan" }
|
||||
strategist_model = { model = "kigi-composer-2.5-fast", agent_type = "cursor" }
|
||||
|
||||
[[goal.skeptic_models]]
|
||||
model = "grok-build"
|
||||
agent_type = "grok-build-plan"
|
||||
model = "kigi"
|
||||
agent_type = "kigi-plan"
|
||||
|
||||
[[goal.skeptic_models]]
|
||||
model = "grok-composer-2.5-fast"
|
||||
model = "kigi-composer-2.5-fast"
|
||||
agent_type = "cursor"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let cfg = Config::new_from_toml_cfg(&raw).expect("[goal] config must parse");
|
||||
let grok_build = crate::util::config::GoalRoleModel {
|
||||
model: "grok-build".into(),
|
||||
agent_type: "grok-build-plan".into(),
|
||||
let kigi = crate::util::config::GoalRoleModel {
|
||||
model: "kigi".into(),
|
||||
agent_type: "kigi-plan".into(),
|
||||
};
|
||||
let composer = crate::util::config::GoalRoleModel {
|
||||
model: "grok-composer-2.5-fast".into(),
|
||||
model: "kigi-composer-2.5-fast".into(),
|
||||
agent_type: "cursor".into(),
|
||||
};
|
||||
let goal_enabled = cfg.resolve_goal().value;
|
||||
@@ -7814,10 +7826,7 @@ agent_type = "cursor"
|
||||
let use_current = cfg.resolve_goal_use_current_model_only().value;
|
||||
assert!(!use_current);
|
||||
let planner = cfg.resolve_goal_planner_model(use_current);
|
||||
assert_eq!(
|
||||
planner.value,
|
||||
GoalRoleModelChoice::Explicit(grok_build.clone())
|
||||
);
|
||||
assert_eq!(planner.value, GoalRoleModelChoice::Explicit(kigi.clone()));
|
||||
assert_eq!(planner.source, ConfigSource::Config);
|
||||
assert_eq!(
|
||||
cfg.resolve_goal_strategist_model(use_current).value,
|
||||
@@ -7826,7 +7835,7 @@ agent_type = "cursor"
|
||||
assert_eq!(
|
||||
cfg.resolve_goal_skeptic_models(use_current).value,
|
||||
vec![
|
||||
GoalRoleModelChoice::Explicit(grok_build),
|
||||
GoalRoleModelChoice::Explicit(kigi),
|
||||
GoalRoleModelChoice::Explicit(composer),
|
||||
]
|
||||
);
|
||||
@@ -7910,7 +7919,7 @@ agent_type = "cursor"
|
||||
management_api_key = "mgmt-key"
|
||||
gcs_service_account_key = "gcs-key"
|
||||
[models]
|
||||
default = "grok-3"
|
||||
default = "kigi-3"
|
||||
[ui]
|
||||
yolo = true
|
||||
theme = "dark"
|
||||
@@ -8532,25 +8541,25 @@ hooks = true
|
||||
let mut value: toml::Value = toml::from_str(
|
||||
r#"
|
||||
[models]
|
||||
default = "grok-build"
|
||||
default = "kigi"
|
||||
|
||||
[[version_overrides]]
|
||||
minimum_version = "1.8.0"
|
||||
[version_overrides.models]
|
||||
default = "grok-4.5"
|
||||
default = "kigi-4.5"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let v = semver::Version::parse("1.8.0").unwrap();
|
||||
kigi_config::apply_version_overrides(&mut value, &v).unwrap();
|
||||
let cfg = Config::new_from_toml_cfg(&value).unwrap();
|
||||
assert_eq!(cfg.models.default.as_deref(), Some("grok-4.5"));
|
||||
assert_eq!(cfg.models.default.as_deref(), Some("kigi-4.5"));
|
||||
}
|
||||
/// Reproduce the enterprise managed config bug: [model.kigi-build] sets
|
||||
/// context_window=500k for model="grok-4.5", but
|
||||
/// [models].default="grok-4.5" resolves to the bare
|
||||
/// context_window=500k for model="kigi-4.5", but
|
||||
/// [models].default="kigi-4.5" resolves to the bare
|
||||
/// prefetched entry (256k) because Layer 3 only overrides key
|
||||
/// "kigi-build", not key "grok-4.5".
|
||||
/// "kigi-build", not key "kigi-4.5".
|
||||
///
|
||||
/// After the Layer 4 slug propagation fix, both keys should have 500k.
|
||||
#[test]
|
||||
@@ -8559,10 +8568,10 @@ default = "grok-4.5"
|
||||
let raw: toml::Value = toml::from_str(
|
||||
r#"
|
||||
[models]
|
||||
default = "grok-4.5"
|
||||
default = "kigi-4.5"
|
||||
|
||||
[model.kigi-build]
|
||||
model = "grok-4.5"
|
||||
model = "kigi-4.5"
|
||||
context_window = 500000
|
||||
base_url = "https://inference.example.com/v1"
|
||||
api_backend = "responses"
|
||||
@@ -8572,25 +8581,25 @@ default = "grok-4.5"
|
||||
let cfg = Config::new_from_toml_cfg(&raw).expect("config should parse");
|
||||
let mut prefetched = IndexMap::new();
|
||||
let mut entry = test_model_entry(
|
||||
"grok-4.5",
|
||||
"kigi-4.5",
|
||||
"https://inference.example.com/v1",
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
entry.info.context_window = NonZeroU64::new(default_cw).unwrap();
|
||||
prefetched.insert("grok-4.5".to_owned(), entry);
|
||||
prefetched.insert("kigi-4.5".to_owned(), entry);
|
||||
let resolved = resolve_model_list(&cfg, Some(prefetched));
|
||||
let by_key = resolved
|
||||
.get("kigi-build")
|
||||
.expect("kigi-build key must exist");
|
||||
assert_eq!(by_key.info.context_window.get(), 500_000);
|
||||
assert_eq!(by_key.info.model, "grok-4.5");
|
||||
let by_latest = resolved.get("grok-4.5").expect("grok-4.5 key must exist");
|
||||
assert_eq!(by_key.info.model, "kigi-4.5");
|
||||
let by_latest = resolved.get("kigi-4.5").expect("kigi-4.5 key must exist");
|
||||
assert_eq!(
|
||||
by_latest.info.context_window.get(),
|
||||
500_000,
|
||||
"BUG: prefetched 'grok-4.5' should inherit 500k from \
|
||||
"BUG: prefetched 'kigi-4.5' should inherit 500k from \
|
||||
sibling 'kigi-build' (same model slug), not stay at {default_cw}"
|
||||
);
|
||||
}
|
||||
@@ -8601,24 +8610,24 @@ default = "grok-4.5"
|
||||
let raw: toml::Value = toml::from_str(
|
||||
r#"
|
||||
[model.kigi-build]
|
||||
model = "grok-4.5"
|
||||
model = "kigi-4.5"
|
||||
context_window = 500000
|
||||
base_url = "https://test.example.com/v1"
|
||||
api_backend = "responses"
|
||||
agent_type = "grok-build"
|
||||
agent_type = "kigi"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let cfg = Config::new_from_toml_cfg(&raw).expect("config should parse");
|
||||
let mut prefetched = IndexMap::new();
|
||||
let mut entry =
|
||||
test_model_entry("grok-4.5", "https://test.example.com/v1", None, None, None);
|
||||
test_model_entry("kigi-4.5", "https://test.example.com/v1", None, None, None);
|
||||
entry.info.context_window = NonZeroU64::new(default_cw).unwrap();
|
||||
entry.info.agent_type = default_agent_type();
|
||||
entry.info.api_backend = ApiBackend::default();
|
||||
prefetched.insert("grok-4.5".to_owned(), entry);
|
||||
prefetched.insert("kigi-4.5".to_owned(), entry);
|
||||
let resolved = resolve_model_list(&cfg, Some(prefetched));
|
||||
let latest = resolved.get("grok-4.5").unwrap();
|
||||
let latest = resolved.get("kigi-4.5").unwrap();
|
||||
assert_eq!(
|
||||
latest.info.agent_type,
|
||||
default_agent_type(),
|
||||
@@ -8637,7 +8646,7 @@ default = "grok-4.5"
|
||||
let raw: toml::Value = toml::from_str(
|
||||
r#"
|
||||
[model.kigi-build]
|
||||
model = "grok-4.5"
|
||||
model = "kigi-4.5"
|
||||
context_window = 500000
|
||||
base_url = "https://test.example.com/v1"
|
||||
"#,
|
||||
@@ -8646,11 +8655,11 @@ default = "grok-4.5"
|
||||
let cfg = Config::new_from_toml_cfg(&raw).expect("config should parse");
|
||||
let mut prefetched = IndexMap::new();
|
||||
let mut entry =
|
||||
test_model_entry("grok-4.5", "https://test.example.com/v1", None, None, None);
|
||||
test_model_entry("kigi-4.5", "https://test.example.com/v1", None, None, None);
|
||||
entry.info.context_window = NonZeroU64::new(65_536).unwrap();
|
||||
prefetched.insert("grok-4.5".to_owned(), entry);
|
||||
prefetched.insert("kigi-4.5".to_owned(), entry);
|
||||
let resolved = resolve_model_list(&cfg, Some(prefetched));
|
||||
let latest = resolved.get("grok-4.5").unwrap();
|
||||
let latest = resolved.get("kigi-4.5").unwrap();
|
||||
assert_eq!(
|
||||
latest.info.context_window.get(),
|
||||
65_536,
|
||||
@@ -9017,13 +9026,13 @@ default = "grok-4.5"
|
||||
fn resolve_model_list_inherits_agent_type_and_api_backend() {
|
||||
let cfg = Config::default();
|
||||
let default_cw = DEFAULT_CONTEXT_WINDOW;
|
||||
let entry = prefetch_model_entry("grok-build", default_cw, ApiBackend::default());
|
||||
let entry = prefetch_model_entry("kigi", default_cw, ApiBackend::default());
|
||||
let mut prefetched = IndexMap::new();
|
||||
prefetched.insert("grok-build".to_owned(), entry);
|
||||
prefetched.insert("kigi".to_owned(), entry);
|
||||
let resolved = resolve_model_list(&cfg, Some(prefetched));
|
||||
let entry = resolved.get("grok-build").expect("model must exist");
|
||||
let entry = resolved.get("kigi").expect("model must exist");
|
||||
let defaults = default_model_entries(&EndpointsConfig::default());
|
||||
if let Some(default) = defaults.get("grok-build") {
|
||||
if let Some(default) = defaults.get("kigi") {
|
||||
if default.info.agent_type != DEFAULT_AGENT_TYPE {
|
||||
assert_eq!(
|
||||
entry.info.agent_type, default.info.agent_type,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
//! parsed again. Non-table values are dropped with a warning.
|
||||
//!
|
||||
//! Warnings are retained on `Config::model_override_warnings` and surfaced by
|
||||
//! `grok inspect`.
|
||||
//! `kigi inspect`.
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use serde::Serialize;
|
||||
@@ -125,7 +125,7 @@ pub(crate) fn log_model_override_warnings(warnings: &[ModelOverrideWarning]) {
|
||||
if !warnings.is_empty() {
|
||||
tracing::warn!(
|
||||
warnings = warnings.len(),
|
||||
"model_override: parsed with warnings; run `grok inspect` for details"
|
||||
"model_override: parsed with warnings; run `kigi inspect` for details"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -292,8 +292,8 @@ mod tests {
|
||||
fn duplicate_compactions_keys_keeps_model() {
|
||||
let cfg = parse_cfg(
|
||||
r#"
|
||||
[model."grok-4.5"]
|
||||
model = "grok-4.5"
|
||||
[model."kigi-4.5"]
|
||||
model = "kigi-4.5"
|
||||
env_key = "ANTHROPIC_AUTH_TOKEN"
|
||||
compactions_remaining = 1
|
||||
send_compactions_remaining = true
|
||||
@@ -301,8 +301,8 @@ mod tests {
|
||||
);
|
||||
let model = cfg
|
||||
.config_models
|
||||
.get("grok-4.5")
|
||||
.expect("grok-4.5 must remain in catalog");
|
||||
.get("kigi-4.5")
|
||||
.expect("kigi-4.5 must remain in catalog");
|
||||
assert_eq!(
|
||||
model.compactions_remaining,
|
||||
Some(CompactionsRemaining::Fixed(1))
|
||||
@@ -312,19 +312,19 @@ mod tests {
|
||||
&& w.field.as_deref() == Some("send_compactions_remaining")
|
||||
}));
|
||||
let resolved = crate::agent::config::resolve_model_list(&cfg, None);
|
||||
assert!(resolved.contains_key("grok-4.5"));
|
||||
assert!(resolved.contains_key("kigi-4.5"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_alias_alone_parses_without_warning() {
|
||||
let cfg = parse_cfg(
|
||||
r#"
|
||||
[model."grok-4.5"]
|
||||
model = "grok-4.5"
|
||||
[model."kigi-4.5"]
|
||||
model = "kigi-4.5"
|
||||
send_compactions_remaining = 2
|
||||
"#,
|
||||
);
|
||||
let model = cfg.config_models.get("grok-4.5").unwrap();
|
||||
let model = cfg.config_models.get("kigi-4.5").unwrap();
|
||||
assert_eq!(
|
||||
model.compactions_remaining,
|
||||
Some(CompactionsRemaining::Fixed(2))
|
||||
@@ -336,17 +336,17 @@ mod tests {
|
||||
fn invalid_reasoning_effort_skips_field_keeps_model() {
|
||||
let cfg = parse_cfg(
|
||||
r#"
|
||||
[model."grok-4.5"]
|
||||
model = "grok-4.5"
|
||||
[model."kigi-4.5"]
|
||||
model = "kigi-4.5"
|
||||
env_key = "ANTHROPIC_AUTH_TOKEN"
|
||||
reasoning_effort = "not-a-level"
|
||||
"#,
|
||||
);
|
||||
let model = cfg
|
||||
.config_models
|
||||
.get("grok-4.5")
|
||||
.expect("grok-4.5 must remain in catalog");
|
||||
assert_eq!(model.model.as_deref(), Some("grok-4.5"));
|
||||
.get("kigi-4.5")
|
||||
.expect("kigi-4.5 must remain in catalog");
|
||||
assert_eq!(model.model.as_deref(), Some("kigi-4.5"));
|
||||
assert!(model.reasoning_effort.is_none());
|
||||
assert!(cfg.model_override_warnings.iter().any(|w| {
|
||||
w.kind == ModelOverrideWarningKind::InvalidValue
|
||||
@@ -358,14 +358,14 @@ mod tests {
|
||||
fn unknown_field_warns_but_keeps_known_fields() {
|
||||
let (models, warnings) = parse_raw(
|
||||
r#"
|
||||
[model."grok-4.5"]
|
||||
model = "grok-4.5"
|
||||
[model."kigi-4.5"]
|
||||
model = "kigi-4.5"
|
||||
env_key = "TOKEN"
|
||||
future_field = 1
|
||||
"#,
|
||||
);
|
||||
let entry = models.get("grok-4.5").unwrap();
|
||||
assert_eq!(entry.model.as_deref(), Some("grok-4.5"));
|
||||
let entry = models.get("kigi-4.5").unwrap();
|
||||
assert_eq!(entry.model.as_deref(), Some("kigi-4.5"));
|
||||
assert_eq!(
|
||||
entry.env_key.as_ref().and_then(|k| k.primary()),
|
||||
Some("TOKEN")
|
||||
@@ -373,7 +373,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
warnings,
|
||||
vec![ModelOverrideWarning {
|
||||
model_key: Some("grok-4.5".to_owned()),
|
||||
model_key: Some("kigi-4.5".to_owned()),
|
||||
field: Some("future_field".to_owned()),
|
||||
kind: ModelOverrideWarningKind::UnknownField,
|
||||
reason: "unknown field".to_owned(),
|
||||
@@ -472,7 +472,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn non_table_model_section_warns_and_is_ignored() {
|
||||
let (models, warnings) = parse_raw(r#"model = "grok-4""#);
|
||||
let (models, warnings) = parse_raw(r#"model = "kigi-4""#);
|
||||
assert!(models.is_empty());
|
||||
assert_eq!(warnings.len(), 1);
|
||||
assert_eq!(warnings[0].kind, ModelOverrideWarningKind::NotATable);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
use crate::session::SessionCommand;
|
||||
|
||||
/// Parse a `x.ai/queue/{remove,reorder,clear,edit,interject}` ext-notification's
|
||||
/// Parse a `kigi/queue/{remove,reorder,clear,edit,interject}` ext-notification's
|
||||
/// params into the corresponding [`SessionCommand`].
|
||||
/// `owner` is the resolved attribution (params `owner`/`clientIdentifier`) used
|
||||
/// to scope remove/clear to the requesting client's own items, and recorded as
|
||||
@@ -17,7 +17,7 @@ pub(super) fn parse_queue_edit_command(
|
||||
owner: Option<String>,
|
||||
) -> Option<SessionCommand> {
|
||||
match method {
|
||||
"x.ai/queue/remove" => {
|
||||
"kigi/queue/remove" => {
|
||||
let id = params.get("id").and_then(|v| v.as_str())?.to_string();
|
||||
// The client supplies the version it last saw; the handler removes
|
||||
// only on an exact match (stale = benign no-op + rebroadcast).
|
||||
@@ -32,7 +32,7 @@ pub(super) fn parse_queue_edit_command(
|
||||
owner,
|
||||
})
|
||||
}
|
||||
"x.ai/queue/reorder" => {
|
||||
"kigi/queue/reorder" => {
|
||||
let ordered_ids = params
|
||||
.get("orderedIds")
|
||||
.and_then(|v| v.as_array())
|
||||
@@ -44,8 +44,8 @@ pub(super) fn parse_queue_edit_command(
|
||||
.unwrap_or_default();
|
||||
Some(SessionCommand::ReorderQueue { ordered_ids })
|
||||
}
|
||||
"x.ai/queue/clear" => Some(SessionCommand::ClearQueue { owner }),
|
||||
"x.ai/queue/interject" => {
|
||||
"kigi/queue/clear" => Some(SessionCommand::ClearQueue { owner }),
|
||||
"kigi/queue/interject" => {
|
||||
let id = params.get("id").and_then(|v| v.as_str())?.to_string();
|
||||
// The client supplies the version it last saw; the handler acts
|
||||
// only on an exact match (stale = benign no-op + rebroadcast).
|
||||
@@ -68,7 +68,7 @@ pub(super) fn parse_queue_edit_command(
|
||||
new_text,
|
||||
})
|
||||
}
|
||||
"x.ai/queue/edit" => {
|
||||
"kigi/queue/edit" => {
|
||||
let id = params.get("id").and_then(|v| v.as_str())?.to_string();
|
||||
let new_text = params.get("newText").and_then(|v| v.as_str())?.to_string();
|
||||
// `owner` is the resolved attribution; for edit it represents the
|
||||
@@ -88,7 +88,7 @@ pub(super) fn parse_queue_edit_command(
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Each `x.ai/queue/*` ext-notification maps to the
|
||||
/// Each `kigi/queue/*` ext-notification maps to the
|
||||
/// correct versioned/idempotent `SessionCommand`.
|
||||
#[test]
|
||||
fn parse_queue_edit_command_maps_each_method() {
|
||||
@@ -96,7 +96,7 @@ mod tests {
|
||||
let p = serde_json::json!({
|
||||
"sessionId": "s1", "id": "p7", "expectedVersion": 3
|
||||
});
|
||||
match parse_queue_edit_command("x.ai/queue/remove", &p, Some("grok-tui".into())) {
|
||||
match parse_queue_edit_command("kigi/queue/remove", &p, Some("kigi-tui".into())) {
|
||||
Some(SessionCommand::RemoveQueuedPrompt {
|
||||
id,
|
||||
expected_version,
|
||||
@@ -104,14 +104,14 @@ mod tests {
|
||||
}) => {
|
||||
assert_eq!(id, "p7");
|
||||
assert_eq!(expected_version, 3);
|
||||
assert_eq!(owner.as_deref(), Some("grok-tui"));
|
||||
assert_eq!(owner.as_deref(), Some("kigi-tui"));
|
||||
}
|
||||
_ => panic!("expected RemoveQueuedPrompt"),
|
||||
}
|
||||
|
||||
// remove without expectedVersion defaults to 0.
|
||||
let p = serde_json::json!({ "sessionId": "s1", "id": "p8" });
|
||||
match parse_queue_edit_command("x.ai/queue/remove", &p, None) {
|
||||
match parse_queue_edit_command("kigi/queue/remove", &p, None) {
|
||||
Some(SessionCommand::RemoveQueuedPrompt {
|
||||
expected_version, ..
|
||||
}) => assert_eq!(expected_version, 0),
|
||||
@@ -120,7 +120,7 @@ mod tests {
|
||||
|
||||
// reorder: orderedIds array.
|
||||
let p = serde_json::json!({ "sessionId": "s1", "orderedIds": ["a", "b", "c"] });
|
||||
match parse_queue_edit_command("x.ai/queue/reorder", &p, None) {
|
||||
match parse_queue_edit_command("kigi/queue/reorder", &p, None) {
|
||||
Some(SessionCommand::ReorderQueue { ordered_ids }) => {
|
||||
assert_eq!(ordered_ids, vec!["a", "b", "c"]);
|
||||
}
|
||||
@@ -129,12 +129,12 @@ mod tests {
|
||||
|
||||
// clear: owner-scoped.
|
||||
match parse_queue_edit_command(
|
||||
"x.ai/queue/clear",
|
||||
"kigi/queue/clear",
|
||||
&serde_json::json!({ "sessionId": "s1" }),
|
||||
Some("grok-tui".into()),
|
||||
Some("kigi-tui".into()),
|
||||
) {
|
||||
Some(SessionCommand::ClearQueue { owner }) => {
|
||||
assert_eq!(owner.as_deref(), Some("grok-tui"));
|
||||
assert_eq!(owner.as_deref(), Some("kigi-tui"));
|
||||
}
|
||||
_ => panic!("expected ClearQueue"),
|
||||
}
|
||||
@@ -143,7 +143,7 @@ mod tests {
|
||||
let p = serde_json::json!({
|
||||
"sessionId": "s1", "id": "p9", "newText": "replacement text"
|
||||
});
|
||||
match parse_queue_edit_command("x.ai/queue/edit", &p, Some("grok-vscode".into())) {
|
||||
match parse_queue_edit_command("kigi/queue/edit", &p, Some("kigi-vscode".into())) {
|
||||
Some(SessionCommand::EditQueuedPrompt {
|
||||
id,
|
||||
new_text,
|
||||
@@ -151,14 +151,14 @@ mod tests {
|
||||
}) => {
|
||||
assert_eq!(id, "p9");
|
||||
assert_eq!(new_text, "replacement text");
|
||||
assert_eq!(editor.as_deref(), Some("grok-vscode"));
|
||||
assert_eq!(editor.as_deref(), Some("kigi-vscode"));
|
||||
}
|
||||
_ => panic!("expected EditQueuedPrompt"),
|
||||
}
|
||||
|
||||
// edit without editor (no owner/clientIdentifier) → editor: None.
|
||||
match parse_queue_edit_command(
|
||||
"x.ai/queue/edit",
|
||||
"kigi/queue/edit",
|
||||
&serde_json::json!({ "sessionId": "s1", "id": "p9", "newText": "x" }),
|
||||
None,
|
||||
) {
|
||||
@@ -171,7 +171,7 @@ mod tests {
|
||||
// edit without newText → None (can't replace text we don't have).
|
||||
assert!(
|
||||
parse_queue_edit_command(
|
||||
"x.ai/queue/edit",
|
||||
"kigi/queue/edit",
|
||||
&serde_json::json!({ "sessionId": "s1", "id": "p9" }),
|
||||
None,
|
||||
)
|
||||
@@ -181,7 +181,7 @@ mod tests {
|
||||
// edit without id → None (can't target an entry).
|
||||
assert!(
|
||||
parse_queue_edit_command(
|
||||
"x.ai/queue/edit",
|
||||
"kigi/queue/edit",
|
||||
&serde_json::json!({ "sessionId": "s1", "newText": "x" }),
|
||||
None,
|
||||
)
|
||||
@@ -192,7 +192,7 @@ mod tests {
|
||||
let p = serde_json::json!({
|
||||
"sessionId": "s1", "id": "p10", "expectedVersion": 2
|
||||
});
|
||||
match parse_queue_edit_command("x.ai/queue/interject", &p, Some("grok-tui".into())) {
|
||||
match parse_queue_edit_command("kigi/queue/interject", &p, Some("kigi-tui".into())) {
|
||||
Some(SessionCommand::InterjectQueuedPrompt {
|
||||
id,
|
||||
expected_version,
|
||||
@@ -201,7 +201,7 @@ mod tests {
|
||||
}) => {
|
||||
assert_eq!(id, "p10");
|
||||
assert_eq!(expected_version, 2);
|
||||
assert_eq!(owner.as_deref(), Some("grok-tui"));
|
||||
assert_eq!(owner.as_deref(), Some("kigi-tui"));
|
||||
assert_eq!(new_text, None, "newText absent → None");
|
||||
}
|
||||
_ => panic!("expected InterjectQueuedPrompt"),
|
||||
@@ -211,7 +211,7 @@ mod tests {
|
||||
let p = serde_json::json!({
|
||||
"sessionId": "s1", "id": "p10", "expectedVersion": 2, "newText": "edited"
|
||||
});
|
||||
match parse_queue_edit_command("x.ai/queue/interject", &p, None) {
|
||||
match parse_queue_edit_command("kigi/queue/interject", &p, None) {
|
||||
Some(SessionCommand::InterjectQueuedPrompt { new_text, .. }) => {
|
||||
assert_eq!(new_text.as_deref(), Some("edited"));
|
||||
}
|
||||
@@ -222,7 +222,7 @@ mod tests {
|
||||
let p = serde_json::json!({
|
||||
"sessionId": "s1", "id": "p10", "expectedVersion": 2, "newText": " "
|
||||
});
|
||||
match parse_queue_edit_command("x.ai/queue/interject", &p, None) {
|
||||
match parse_queue_edit_command("kigi/queue/interject", &p, None) {
|
||||
Some(SessionCommand::InterjectQueuedPrompt { new_text, .. }) => {
|
||||
assert_eq!(new_text, None, "blank override must be dropped");
|
||||
}
|
||||
@@ -231,7 +231,7 @@ mod tests {
|
||||
|
||||
// interject without expectedVersion defaults to 0.
|
||||
match parse_queue_edit_command(
|
||||
"x.ai/queue/interject",
|
||||
"kigi/queue/interject",
|
||||
&serde_json::json!({ "sessionId": "s1", "id": "p11" }),
|
||||
None,
|
||||
) {
|
||||
@@ -243,17 +243,17 @@ mod tests {
|
||||
|
||||
// interject without id → None (can't target an entry).
|
||||
assert!(
|
||||
parse_queue_edit_command("x.ai/queue/interject", &serde_json::json!({}), None)
|
||||
parse_queue_edit_command("kigi/queue/interject", &serde_json::json!({}), None)
|
||||
.is_none()
|
||||
);
|
||||
|
||||
// unknown method → None.
|
||||
assert!(
|
||||
parse_queue_edit_command("x.ai/queue/bogus", &serde_json::json!({}), None).is_none()
|
||||
parse_queue_edit_command("kigi/queue/bogus", &serde_json::json!({}), None).is_none()
|
||||
);
|
||||
// remove without id → None (can't target an entry).
|
||||
assert!(
|
||||
parse_queue_edit_command("x.ai/queue/remove", &serde_json::json!({}), None).is_none()
|
||||
parse_queue_edit_command("kigi/queue/remove", &serde_json::json!({}), None).is_none()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ pub fn project_scope_allowed(cwd: &Path) -> bool {
|
||||
/// is on, the workspace is NOT store-trusted, and repo-local code-exec configs
|
||||
/// are present (something to gate). Interactivity is forced `true` because the
|
||||
/// caller already confirmed the client can prompt (it advertised
|
||||
/// `x.ai/folderTrust.interactive`); the TTY-based [`decide_inputs`] default is
|
||||
/// `kigi/folderTrust.interactive`); the TTY-based [`decide_inputs`] default is
|
||||
/// false under the ACP stdio transport. Mirrors the [`decide`] precedence so it
|
||||
/// cannot drift from the gate: feature-off (kill-switch / opt-out) / store-trusted
|
||||
/// / no-configs all collapse to a non-`Prompt` verdict and return false.
|
||||
@@ -226,7 +226,7 @@ pub(crate) fn record_for_test(cwd: &Path, allowed: bool) {
|
||||
/// `allow_prompt` must be `true` ONLY where a blocking stdin y/N read is safe —
|
||||
/// i.e. agent `initialize` for the launch directory, before the TUI takes over
|
||||
/// the terminal. Every other call site (per-session cwd, leader-served sessions
|
||||
/// whose cwd differs from the launch dir, `grok mcp doctor`) passes `false`, so
|
||||
/// whose cwd differs from the launch dir, `kigi mcp doctor`) passes `false`, so
|
||||
/// an unresolved interactive-but-untrusted workspace resolves **fail-closed**
|
||||
/// (untrusted, no prompt) — only the launch dir is ever prompted for.
|
||||
pub fn resolve_and_record(cwd: &Path, remote: Option<&RemoteSettings>, allow_prompt: bool) -> bool {
|
||||
@@ -290,7 +290,7 @@ pub fn resolve_launch_dir_trust(cwd: &Path, remote: Option<&RemoteSettings>) ->
|
||||
/// - A cached **grant** (`Some(true)`) is durable and short-circuits — neither
|
||||
/// `store_trusted` nor `recompute` runs.
|
||||
/// - A cached **untrusted** verdict (`Some(false)`) is re-checked via
|
||||
/// `store_trusted`: a `grok --trust` grant issued AFTER this workspace was
|
||||
/// `store_trusted`: a `kigi --trust` grant issued AFTER this workspace was
|
||||
/// first resolved writes the store, so honor it on the next session without a
|
||||
/// restart. Without this re-read a long-lived leader would mask the grant.
|
||||
/// - An **unrecorded** key (`None`) does a full `recompute`, which reports
|
||||
@@ -389,7 +389,7 @@ fn compute_from_inputs(
|
||||
/// merged server list when the workspace is untrusted.
|
||||
///
|
||||
/// SINGLE SOURCE OF TRUTH for "project-scoped MCP names" across ALL gate sites
|
||||
/// (session merge, the session-less agent pool, `grok mcp doctor`). It MUST
|
||||
/// (session merge, the session-less agent pool, `kigi mcp doctor`). It MUST
|
||||
/// enumerate every project MCP source the loaders read; adding a new repo-local
|
||||
/// MCP source without extending this fn silently re-opens the gate (guarded by
|
||||
/// `project_scoped_mcp_names_cover_every_source`).
|
||||
@@ -1190,9 +1190,9 @@ mod tests {
|
||||
// can distinguish it from user/plugin servers. Asserts on the specific
|
||||
// key, so any real `~/.kigi/lsp.json` on the test host is irrelevant.
|
||||
let tmp = repo_tmp();
|
||||
let grok = tmp.path().join(".kigi");
|
||||
std::fs::create_dir_all(&grok).unwrap();
|
||||
std::fs::write(grok.join("lsp.json"), r#"{"projlsp": {"command": "true"}}"#).unwrap();
|
||||
let kigi = tmp.path().join(".kigi");
|
||||
std::fs::create_dir_all(&kigi).unwrap();
|
||||
std::fs::write(kigi.join("lsp.json"), r#"{"projlsp": {"command": "true"}}"#).unwrap();
|
||||
|
||||
let sourced = load_servers_with_plugins_sourced(tmp.path(), &[], &[], &[], &[]);
|
||||
let (_, source) = sourced.get("projlsp").expect("project server present");
|
||||
@@ -1209,9 +1209,9 @@ mod tests {
|
||||
// End-to-end of the load-site gate (Sites A/B): a project server loaded
|
||||
// from `<cwd>/.kigi/lsp.json` is dropped once the workspace is untrusted.
|
||||
let tmp = repo_tmp();
|
||||
let grok = tmp.path().join(".kigi");
|
||||
std::fs::create_dir_all(&grok).unwrap();
|
||||
std::fs::write(grok.join("lsp.json"), r#"{"projlsp": {"command": "true"}}"#).unwrap();
|
||||
let kigi = tmp.path().join(".kigi");
|
||||
std::fs::create_dir_all(&kigi).unwrap();
|
||||
std::fs::write(kigi.join("lsp.json"), r#"{"projlsp": {"command": "true"}}"#).unwrap();
|
||||
|
||||
let sourced = load_servers_with_plugins_sourced(tmp.path(), &[], &[], &[], &[]);
|
||||
assert!(
|
||||
@@ -1243,10 +1243,10 @@ mod tests {
|
||||
r#"{"mcpServers": {"projjson": {"url": "https://proj.example.com/mcp"}}}"#,
|
||||
)
|
||||
.unwrap();
|
||||
let grok = tmp.path().join(".kigi");
|
||||
std::fs::create_dir_all(&grok).unwrap();
|
||||
let kigi = tmp.path().join(".kigi");
|
||||
std::fs::create_dir_all(&kigi).unwrap();
|
||||
std::fs::write(
|
||||
grok.join("config.toml"),
|
||||
kigi.join("config.toml"),
|
||||
"[mcp_servers.projtoml]\nurl = \"https://projtoml.example.com/mcp\"\n",
|
||||
)
|
||||
.unwrap();
|
||||
@@ -1264,10 +1264,10 @@ mod tests {
|
||||
#[test]
|
||||
fn project_scoped_mcp_names_cover_every_source() {
|
||||
let tmp = repo_tmp();
|
||||
let grok = tmp.path().join(".kigi");
|
||||
std::fs::create_dir_all(&grok).unwrap();
|
||||
let kigi = tmp.path().join(".kigi");
|
||||
std::fs::create_dir_all(&kigi).unwrap();
|
||||
std::fs::write(
|
||||
grok.join("config.toml"),
|
||||
kigi.join("config.toml"),
|
||||
"[mcp_servers.cfgsrv]\nurl = \"https://cfg.example.com/mcp\"\n",
|
||||
)
|
||||
.unwrap();
|
||||
@@ -1339,7 +1339,7 @@ mod tests {
|
||||
let key = workspace_key(tmp.path());
|
||||
record(&key, false);
|
||||
assert!(!project_scope_allowed(tmp.path()));
|
||||
// Simulate a `grok --trust` grant landing in the store after the
|
||||
// Simulate a `kigi --trust` grant landing in the store after the
|
||||
// untrusted verdict was cached: the re-read sees trusted, so the next
|
||||
// resolve upgrades the cache without a process restart.
|
||||
let allowed = resolve_and_record_inner(
|
||||
|
||||
@@ -226,7 +226,7 @@ fn broadcast_model_changed(
|
||||
agent
|
||||
.gateway
|
||||
.forward_fire_and_forget(acp::ExtNotification::new(
|
||||
"x.ai/session_notification",
|
||||
"kigi/session_notification",
|
||||
params.into(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -28,27 +28,27 @@ fn backfill_session_summary(summary: &mut Summary) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Router for x.ai/session/* and x.ai/session_summaries/* methods.
|
||||
/// Router for kigi/session/* and kigi/session_summaries/* methods.
|
||||
pub async fn handle(
|
||||
agent: &MvpAgent,
|
||||
args: &acp::ExtRequest,
|
||||
) -> Result<acp::ExtResponse, acp::Error> {
|
||||
match args.method.as_ref() {
|
||||
"x.ai/session/info" => handle_session_info(agent, args).await,
|
||||
"x.ai/session/close" => handle_session_close(agent, args).await,
|
||||
"x.ai/session/list" => handle_session_list(agent, args).await,
|
||||
"x.ai/sessions/list" => handle_roster_list(agent, args).await,
|
||||
m if m.starts_with("x.ai/session_summaries/") => {
|
||||
"kigi/session/info" => handle_session_info(agent, args).await,
|
||||
"kigi/session/close" => handle_session_close(agent, args).await,
|
||||
"kigi/session/list" => handle_session_list(agent, args).await,
|
||||
"kigi/sessions/list" => handle_roster_list(agent, args).await,
|
||||
m if m.starts_with("kigi/session_summaries/") => {
|
||||
handle_session_summaries(agent, args).await
|
||||
}
|
||||
_ => Err(acp::Error::method_not_found()),
|
||||
}
|
||||
}
|
||||
|
||||
/// `x.ai/sessions/list` — the FleetView roster. Returns every
|
||||
/// `kigi/sessions/list` — the FleetView roster. Returns every
|
||||
/// resident session plus recently-touched on-disk `Dormant` sessions. Clients
|
||||
/// poll this while the dashboard is open and reconcile against the
|
||||
/// `x.ai/sessions/changed` broadcast.
|
||||
/// `kigi/sessions/changed` broadcast.
|
||||
async fn handle_roster_list(
|
||||
agent: &MvpAgent,
|
||||
_args: &acp::ExtRequest,
|
||||
@@ -166,7 +166,7 @@ async fn handle_session_close(
|
||||
// (see `MvpAgent::handle_evict_sessions` / `close_session_explicit`).
|
||||
agent.request_session_shutdown(&sid);
|
||||
agent.close_session_explicit(&sid);
|
||||
tracing::info!(session_id = %req.session_id, "session closed via x.ai/session/close");
|
||||
tracing::info!(session_id = %req.session_id, "session closed via kigi/session/close");
|
||||
} else {
|
||||
tracing::debug!(session_id = %req.session_id, "session/close: session not found (already closed)");
|
||||
}
|
||||
@@ -181,7 +181,7 @@ async fn handle_session_summaries(
|
||||
args: &acp::ExtRequest,
|
||||
) -> Result<acp::ExtResponse, acp::Error> {
|
||||
match args.method.as_ref() {
|
||||
"x.ai/session_summaries/session_list" => {
|
||||
"kigi/session_summaries/session_list" => {
|
||||
let req = serde_json::from_str::<SessionListRequest>(args.params.get())?;
|
||||
let cwd = req.workspace_directory.to_string_lossy().to_string();
|
||||
|
||||
@@ -203,7 +203,7 @@ async fn handle_session_summaries(
|
||||
|
||||
Ok(acp::ExtResponse::new(value))
|
||||
}
|
||||
"x.ai/session_summaries/workspace_list" => {
|
||||
"kigi/session_summaries/workspace_list" => {
|
||||
tracing::debug!("xai/session_summaries/workspace_list is working");
|
||||
let _req = serde_json::from_str::<AllSessionOverviewRequest>(args.params.get())?;
|
||||
|
||||
@@ -215,7 +215,7 @@ async fn handle_session_summaries(
|
||||
|
||||
summaries_to_overview_response(summaries)
|
||||
}
|
||||
"x.ai/session_summaries/workspace_list_recent" => {
|
||||
"kigi/session_summaries/workspace_list_recent" => {
|
||||
let req = serde_json::from_str::<RecentSessionsRequest>(args.params.get())?;
|
||||
|
||||
let _timer = crate::instrumentation_timer!("session.list_sessions_recent");
|
||||
|
||||
@@ -76,7 +76,7 @@ fn resolve_config(cfg: &AgentConfig, auth_manager: &AuthManager) -> AgentConfig
|
||||
crate::util::config::sync_campaign_fields(&mut cfg);
|
||||
crate::agent::config::apply_remote_settings_side_effects(cfg.remote_settings.as_ref());
|
||||
|
||||
// env var > remote settings > Local. Skip remote settings for Generic (grok -p, subagents).
|
||||
// env var > remote settings > Local. Skip remote settings for Generic (kigi -p, subagents).
|
||||
if cfg.storage_mode == StorageMode::Local
|
||||
&& cfg.mode != crate::agent::config::AgentMode::Generic
|
||||
{
|
||||
@@ -86,7 +86,7 @@ fn resolve_config(cfg: &AgentConfig, auth_manager: &AuthManager) -> AgentConfig
|
||||
if cfg.storage_mode == StorageMode::Writeback
|
||||
&& !auth_manager.current().is_some_and(|a| a.is_session_auth())
|
||||
{
|
||||
tracing::info!("Writeback is disabled: requires auth with grok.com");
|
||||
tracing::info!("Writeback is disabled: requires auth with kimi.com");
|
||||
cfg.storage_mode = StorageMode::Local;
|
||||
}
|
||||
|
||||
|
||||
@@ -405,7 +405,7 @@ impl ModelsManager {
|
||||
self.reselect_current_model_if_missing(&new_config);
|
||||
}
|
||||
|
||||
// Push the new catalog to connected clients (`x.ai/models/update`).
|
||||
// Push the new catalog to connected clients (`kigi/models/update`).
|
||||
// Without this, a long-running agent (leader mode) correctly swaps
|
||||
// its in-memory catalog on a config.toml `[model.*]`/`[models]` edit,
|
||||
// but already-connected clients keep rendering the stale model list
|
||||
@@ -592,10 +592,10 @@ impl ModelsManager {
|
||||
|
||||
/// Catalog opt-in to display the served-checkpoint fingerprint for this model.
|
||||
///
|
||||
/// `model_id` may be a routing slug (`config.model`, e.g. `grok-4.5`)
|
||||
/// `model_id` may be a routing slug (`config.model`, e.g. `kigi-4.5`)
|
||||
/// OR a catalog key; the catalog map is keyed by the config key, which can
|
||||
/// differ from the slug for custom/enterprise ids (e.g. key `enterprise-grok-build`
|
||||
/// → slug `grok-4.5`). Resolve to the catalog key first so a slug
|
||||
/// differ from the slug for custom/enterprise ids (e.g. key `enterprise-kigi`
|
||||
/// → slug `kigi-4.5`). Resolve to the catalog key first so a slug
|
||||
/// caller still finds the opted-in entry.
|
||||
pub fn model_show_model_fingerprint(&self, model_id: &str) -> bool {
|
||||
let models = self.inner.models.read();
|
||||
@@ -737,7 +737,7 @@ impl ModelsManager {
|
||||
acp::SessionModelState::new(current, available.values().cloned().collect());
|
||||
if let Ok(params) = serde_json::value::to_raw_value(&model_state) {
|
||||
gw.forward_fire_and_forget(acp::ExtNotification::new(
|
||||
"x.ai/models/update",
|
||||
"kigi/models/update",
|
||||
params.into(),
|
||||
));
|
||||
}
|
||||
@@ -749,8 +749,8 @@ impl ModelsManager {
|
||||
///
|
||||
/// A long-running leader otherwise only refreshes its catalog from its
|
||||
/// *own* fetch paths (startup prefetch, auth change, response-header etag).
|
||||
/// When another grok process sharing `~/.kigi` (a `--no-leader` run, a
|
||||
/// newer client, grok-desktop) fetches a fresher `/v1/models` catalog and
|
||||
/// When another kigi process sharing `~/.kigi` (a `--no-leader` run, a
|
||||
/// newer client, kigi-desktop) fetches a fresher `/v1/models` catalog and
|
||||
/// persists it, this picks it up without a network round-trip.
|
||||
///
|
||||
/// Guards, in order:
|
||||
@@ -934,7 +934,7 @@ impl ModelsManager {
|
||||
/// delivering events on macOS after resume from sleep. On each
|
||||
/// notification the catalog is re-fetched from the server; if the
|
||||
/// fetch succeeds and the catalog changed, clients are notified
|
||||
/// via `x.ai/models/update`.
|
||||
/// via `kigi/models/update`.
|
||||
pub fn start_auth_refresh_watcher(&self, notify: Arc<tokio::sync::Notify>) {
|
||||
let mgr = self.clone();
|
||||
let had_catalog_at_start = *self.inner.has_fetched_real_catalog.read();
|
||||
@@ -1687,7 +1687,7 @@ struct PrefetchEnv {
|
||||
|
||||
fn resolve_prefetch_env_with_auth(auth: Option<KimiAuth>) -> Option<PrefetchEnv> {
|
||||
let _timer = crate::instrumentation_timer!("startup.early_prefetch_launch");
|
||||
// Config-aware (not env-only) so the prefetch can't leak the bearer to api.x.ai.
|
||||
// Config-aware (not env-only) so the prefetch can't leak the bearer to the BYOK endpoint.
|
||||
let mut endpoints = config::EndpointsConfig::from_effective_config();
|
||||
|
||||
if endpoints.deployment_key.is_none() {
|
||||
@@ -1796,8 +1796,8 @@ fn spawn_prefetch_thread(env: PrefetchEnv) -> EarlyPrefetchHandle {
|
||||
|
||||
/// Map a model id (catalog key or routing slug) to its catalog key.
|
||||
///
|
||||
/// Sessions persist the routing slug (`[model.X].model`, e.g. `grok-4.5`);
|
||||
/// the catalog and `/model` picker use config keys (e.g. `enterprise-grok-build`).
|
||||
/// Sessions persist the routing slug (`[model.X].model`, e.g. `kigi-4.5`);
|
||||
/// the catalog and `/model` picker use config keys (e.g. `enterprise-kigi`).
|
||||
/// Last slug match wins so user overrides beat defaults (matches `MvpAgent::resolve_model_id`).
|
||||
pub(crate) fn resolve_catalog_key(
|
||||
models: &IndexMap<String, ModelEntry>,
|
||||
@@ -2070,7 +2070,7 @@ pub fn resolve_model_catalog(
|
||||
|
||||
// Skip non-reasoning models so we don't send the field to providers that reject it.
|
||||
// Also skip models whose effort menu does not include the override (e.g. `--effort none`
|
||||
// must not stamp `none` onto grok-4.5, which only offers low/medium/high).
|
||||
// must not stamp `none` onto kigi-4.5, which only offers low/medium/high).
|
||||
if let Some(effort) = cfg.reasoning_effort_override {
|
||||
for entry in catalog.values_mut() {
|
||||
if model_offers_reasoning_effort(&entry.info, effort) {
|
||||
@@ -2188,7 +2188,7 @@ mod tests {
|
||||
.try_init();
|
||||
// Use a temp dir so AuthManager finds no credentials — ensures
|
||||
// refresh_async bails at the auth check without needing a tokio runtime.
|
||||
let tmp = std::env::temp_dir().join("grok-test-models-manager");
|
||||
let tmp = std::env::temp_dir().join("kigi-test-models-manager");
|
||||
let auth_manager = Arc::new(AuthManager::new(&tmp, KimiCodeConfig::default()));
|
||||
ModelsManager::new(
|
||||
None,
|
||||
@@ -2263,11 +2263,11 @@ mod tests {
|
||||
allowed_models = ["keep-*"]
|
||||
[model.zzz-first]
|
||||
model = "zzz-first"
|
||||
base_url = "https://api.x.ai/v1"
|
||||
base_url = "https://byok.example/v1"
|
||||
context_window = 256000
|
||||
[model.keep-one]
|
||||
model = "keep-one"
|
||||
base_url = "https://api.x.ai/v1"
|
||||
base_url = "https://byok.example/v1"
|
||||
context_window = 256000
|
||||
"#,
|
||||
);
|
||||
@@ -2286,15 +2286,15 @@ mod tests {
|
||||
let excluded = config_from_toml(
|
||||
r#"
|
||||
[models]
|
||||
default = "grok-3"
|
||||
allowed_models = ["grok-4*"]
|
||||
default = "kigi-3"
|
||||
allowed_models = ["kigi-4*"]
|
||||
[model.kigi-3]
|
||||
model = "grok-3"
|
||||
base_url = "https://api.x.ai/v1"
|
||||
model = "kigi-3"
|
||||
base_url = "https://byok.example/v1"
|
||||
context_window = 256000
|
||||
[model.kigi-4]
|
||||
model = "grok-4"
|
||||
base_url = "https://api.x.ai/v1"
|
||||
model = "kigi-4"
|
||||
base_url = "https://byok.example/v1"
|
||||
context_window = 256000
|
||||
"#,
|
||||
);
|
||||
@@ -2302,7 +2302,7 @@ mod tests {
|
||||
assert!(
|
||||
validate_selectable(&excluded, &catalog)
|
||||
.unwrap_err()
|
||||
.contains("grok-3")
|
||||
.contains("kigi-3")
|
||||
);
|
||||
|
||||
// Matches nothing → error.
|
||||
@@ -2311,8 +2311,8 @@ mod tests {
|
||||
[models]
|
||||
allowed_models = ["nomatch-*"]
|
||||
[model.kigi-4]
|
||||
model = "grok-4"
|
||||
base_url = "https://api.x.ai/v1"
|
||||
model = "kigi-4"
|
||||
base_url = "https://byok.example/v1"
|
||||
context_window = 256000
|
||||
"#,
|
||||
);
|
||||
@@ -2362,7 +2362,7 @@ mod tests {
|
||||
);
|
||||
|
||||
// Real switch: both subscribers see the change.
|
||||
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
|
||||
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
|
||||
tokio::time::timeout(std::time::Duration::from_millis(100), rx_a.changed())
|
||||
.await
|
||||
.expect("rx_a saw the switch")
|
||||
@@ -2380,13 +2380,13 @@ mod tests {
|
||||
async fn model_switch_generation_snapshot_reflects_current_state() {
|
||||
let mgr = test_manager();
|
||||
let start = mgr.model_switch_generation();
|
||||
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
|
||||
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
|
||||
assert_eq!(mgr.model_switch_generation(), start + 1);
|
||||
// Idempotent: same id → no bump.
|
||||
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
|
||||
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
|
||||
assert_eq!(mgr.model_switch_generation(), start + 1);
|
||||
// Another real change: another bump.
|
||||
mgr.set_current_model_id(acp::ModelId::new("grok-3"));
|
||||
mgr.set_current_model_id(acp::ModelId::new("kigi-3"));
|
||||
assert_eq!(mgr.model_switch_generation(), start + 2);
|
||||
}
|
||||
|
||||
@@ -2430,7 +2430,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn current_reasoning_effort_seeded_from_config() {
|
||||
let tmp = std::env::temp_dir().join("grok-test-models-manager-seed");
|
||||
let tmp = std::env::temp_dir().join("kigi-test-models-manager-seed");
|
||||
let auth_manager = Arc::new(AuthManager::new(&tmp, KimiCodeConfig::default()));
|
||||
let mut cfg = config::Config::default();
|
||||
cfg.models.default_reasoning_effort = Some(ReasoningEffort::Xhigh);
|
||||
@@ -2504,7 +2504,7 @@ mod tests {
|
||||
let mut prefetched = IndexMap::new();
|
||||
// 4.5-style: supports effort, menu is high only (no none).
|
||||
let mut no_none = ModelEntry {
|
||||
info: config::ModelInfo::fallback("grok-4.5"),
|
||||
info: config::ModelInfo::fallback("kigi-4.5"),
|
||||
api_key: None,
|
||||
env_key: None,
|
||||
api_base_url: None,
|
||||
@@ -2518,7 +2518,7 @@ mod tests {
|
||||
default: true,
|
||||
}];
|
||||
no_none.info.reasoning_effort = Some(ReasoningEffort::High);
|
||||
prefetched.insert("grok-4.5".to_string(), no_none);
|
||||
prefetched.insert("kigi-4.5".to_string(), no_none);
|
||||
|
||||
// Model that explicitly offers none.
|
||||
let mut with_none = ModelEntry {
|
||||
@@ -2539,7 +2539,7 @@ mod tests {
|
||||
|
||||
let catalog = resolve_model_catalog(&cfg, Some(prefetched));
|
||||
assert_eq!(
|
||||
catalog["grok-4.5"].info.reasoning_effort,
|
||||
catalog["kigi-4.5"].info.reasoning_effort,
|
||||
Some(ReasoningEffort::High),
|
||||
"--effort none must not stamp onto models that do not offer none"
|
||||
);
|
||||
@@ -2598,7 +2598,7 @@ mod tests {
|
||||
assert_eq!(catalog["plain"].info.reasoning_effort, None);
|
||||
|
||||
// The internal getters read those derived fields.
|
||||
let tmp = std::env::temp_dir().join("grok-test-models-manager-menu-only");
|
||||
let tmp = std::env::temp_dir().join("kigi-test-models-manager-menu-only");
|
||||
let auth_manager = Arc::new(AuthManager::new(&tmp, KimiCodeConfig::default()));
|
||||
let mgr = ModelsManager::new(
|
||||
None,
|
||||
@@ -2698,37 +2698,37 @@ mod tests {
|
||||
fn first_apply_refresh_reselects_default_model() {
|
||||
let mgr = test_manager();
|
||||
let mut cfg = config::Config::default();
|
||||
cfg.models.default = Some("grok-3".to_string());
|
||||
cfg.models.default = Some("kigi-3".to_string());
|
||||
|
||||
assert!(!mgr.has_fetched_real_catalog());
|
||||
|
||||
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
|
||||
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
|
||||
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
|
||||
|
||||
assert!(mgr.has_fetched_real_catalog());
|
||||
assert_eq!(mgr.current_model_id().0.as_ref(), "grok-3");
|
||||
assert_eq!(mgr.current_model_id().0.as_ref(), "kigi-3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subsequent_apply_refresh_preserves_user_model() {
|
||||
let mgr = test_manager();
|
||||
let mut cfg = config::Config::default();
|
||||
cfg.models.default = Some("grok-3".to_string());
|
||||
cfg.models.default = Some("kigi-3".to_string());
|
||||
|
||||
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
|
||||
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
|
||||
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
|
||||
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
|
||||
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
|
||||
|
||||
// Simulate on_auth_changed clearing prefetched + etag.
|
||||
*mgr.inner.prefetched.write() = None;
|
||||
*mgr.inner.etag.write() = None;
|
||||
|
||||
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
|
||||
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
|
||||
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
|
||||
|
||||
assert_eq!(
|
||||
mgr.current_model_id().0.as_ref(),
|
||||
"grok-4",
|
||||
"kigi-4",
|
||||
"user's model selection must survive auth-change refresh"
|
||||
);
|
||||
}
|
||||
@@ -2737,19 +2737,19 @@ mod tests {
|
||||
fn subsequent_refresh_reselects_when_model_removed() {
|
||||
let mgr = test_manager();
|
||||
let mut cfg = config::Config::default();
|
||||
cfg.models.default = Some("grok-3".to_string());
|
||||
cfg.models.default = Some("kigi-3".to_string());
|
||||
|
||||
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
|
||||
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
|
||||
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
|
||||
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
|
||||
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
|
||||
|
||||
// Second refresh with grok-4 removed.
|
||||
let prefetched = make_prefetched(&["grok-3", "grok-4.5"]);
|
||||
// Second refresh with kigi-4 removed.
|
||||
let prefetched = make_prefetched(&["kigi-3", "kigi-4.5"]);
|
||||
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
|
||||
|
||||
assert_eq!(
|
||||
mgr.current_model_id().0.as_ref(),
|
||||
"grok-3",
|
||||
"kigi-3",
|
||||
"should fall back to config default when current is removed"
|
||||
);
|
||||
}
|
||||
@@ -2773,11 +2773,11 @@ mod tests {
|
||||
fn apply_config_honors_new_preferred_model() {
|
||||
let mgr = test_manager();
|
||||
let mut cfg = config::Config::default();
|
||||
cfg.models.default = Some("grok-3".to_string());
|
||||
cfg.models.default = Some("kigi-3".to_string());
|
||||
|
||||
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
|
||||
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
|
||||
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
|
||||
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
|
||||
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
|
||||
|
||||
// Simulate stale inner cfg (no default) from a racing auth refresh.
|
||||
let mut stale_cfg = config::Config::default();
|
||||
@@ -2785,12 +2785,12 @@ mod tests {
|
||||
*mgr.inner.cfg.write() = stale_cfg;
|
||||
|
||||
let mut new_cfg = config::Config::default();
|
||||
new_cfg.models.default = Some("grok-3".to_string());
|
||||
new_cfg.models.default = Some("kigi-3".to_string());
|
||||
mgr.apply_config(new_cfg);
|
||||
|
||||
assert_eq!(
|
||||
mgr.current_model_id().0.as_ref(),
|
||||
"grok-3",
|
||||
"kigi-3",
|
||||
"apply_config must honor updated preferred model from config"
|
||||
);
|
||||
}
|
||||
@@ -2800,10 +2800,10 @@ mod tests {
|
||||
let mgr = test_manager();
|
||||
let cfg = config::Config::default();
|
||||
|
||||
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
|
||||
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
|
||||
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
|
||||
|
||||
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
|
||||
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
|
||||
|
||||
// Unrelated config change — preferred model unchanged.
|
||||
let new_cfg = config::Config::default();
|
||||
@@ -2811,7 +2811,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
mgr.current_model_id().0.as_ref(),
|
||||
"grok-4",
|
||||
"kigi-4",
|
||||
"apply_config must not reset model when preferred hasn't changed"
|
||||
);
|
||||
}
|
||||
@@ -2820,16 +2820,16 @@ mod tests {
|
||||
fn apply_config_falls_back_when_preferred_not_in_catalog() {
|
||||
let mgr = test_manager();
|
||||
let mut cfg = config::Config::default();
|
||||
cfg.models.default = Some("grok-3".to_string());
|
||||
cfg.models.default = Some("kigi-3".to_string());
|
||||
|
||||
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
|
||||
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
|
||||
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
|
||||
|
||||
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
|
||||
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
|
||||
|
||||
// Preferred model not in catalog — falls back to first entry.
|
||||
let mut new_cfg = config::Config::default();
|
||||
new_cfg.models.default = Some("grok-nonexistent".to_string());
|
||||
new_cfg.models.default = Some("kigi-nonexistent".to_string());
|
||||
mgr.apply_config(new_cfg);
|
||||
|
||||
let current = mgr.current_model_id();
|
||||
@@ -2845,15 +2845,15 @@ mod tests {
|
||||
fn apply_config_both_none_preferred_preserves_current() {
|
||||
let mgr = test_manager();
|
||||
let cfg = config::Config::default();
|
||||
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
|
||||
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
|
||||
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
|
||||
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
|
||||
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
|
||||
let new_cfg = config::Config::default();
|
||||
mgr.apply_config(new_cfg);
|
||||
|
||||
assert_eq!(
|
||||
mgr.current_model_id().0.as_ref(),
|
||||
"grok-4",
|
||||
"kigi-4",
|
||||
"both-None preferred must preserve user's runtime model"
|
||||
);
|
||||
}
|
||||
@@ -2862,13 +2862,13 @@ mod tests {
|
||||
fn apply_config_old_some_new_none_preserves_current() {
|
||||
let mgr = test_manager();
|
||||
let mut cfg = config::Config::default();
|
||||
cfg.models.default = Some("grok-3".to_string());
|
||||
cfg.models.default = Some("kigi-3".to_string());
|
||||
|
||||
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
|
||||
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
|
||||
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
|
||||
assert_eq!(mgr.current_model_id().0.as_ref(), "grok-3");
|
||||
assert_eq!(mgr.current_model_id().0.as_ref(), "kigi-3");
|
||||
|
||||
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
|
||||
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
|
||||
|
||||
// [models] default removed — is_some() guard prevents reset.
|
||||
let new_cfg = config::Config::default();
|
||||
@@ -2876,7 +2876,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
mgr.current_model_id().0.as_ref(),
|
||||
"grok-4",
|
||||
"kigi-4",
|
||||
"old=Some new=None must not reset model (is_some guard)"
|
||||
);
|
||||
}
|
||||
@@ -2887,29 +2887,29 @@ mod tests {
|
||||
fn auth_refresh_then_config_reload_preserves_user_model() {
|
||||
let mgr = test_manager();
|
||||
let mut cfg = config::Config::default();
|
||||
cfg.models.default = Some("grok-3".to_string());
|
||||
cfg.models.default = Some("kigi-3".to_string());
|
||||
|
||||
// Initial fetch.
|
||||
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
|
||||
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
|
||||
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
|
||||
|
||||
// User runs /model grok-4.
|
||||
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
|
||||
// User runs /model kigi-4.
|
||||
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
|
||||
|
||||
// Auth refresh races — clears prefetched/etag.
|
||||
*mgr.inner.prefetched.write() = None;
|
||||
*mgr.inner.etag.write() = None;
|
||||
|
||||
// Second fetch must preserve user's model.
|
||||
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
|
||||
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
|
||||
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
|
||||
assert_eq!(mgr.current_model_id().0.as_ref(), "grok-4");
|
||||
assert_eq!(mgr.current_model_id().0.as_ref(), "kigi-4");
|
||||
|
||||
// Config reload with persisted preference.
|
||||
let mut new_cfg = config::Config::default();
|
||||
new_cfg.models.default = Some("grok-4".to_string());
|
||||
new_cfg.models.default = Some("kigi-4".to_string());
|
||||
mgr.apply_config(new_cfg);
|
||||
assert_eq!(mgr.current_model_id().0.as_ref(), "grok-4");
|
||||
assert_eq!(mgr.current_model_id().0.as_ref(), "kigi-4");
|
||||
}
|
||||
|
||||
// ── disk-cache hot-reload (external models_cache.json writes) ────
|
||||
@@ -2931,7 +2931,7 @@ mod tests {
|
||||
|
||||
let auth_method = mgr.inner.fetch_auth.read().cache_auth_method();
|
||||
cache.persist(
|
||||
&make_prefetched(&["grok-4.5", "grok-4.3"]),
|
||||
&make_prefetched(&["kigi-4.5", "kigi-4.3"]),
|
||||
Some("etag-ext"),
|
||||
auth_method,
|
||||
&mgr.cache_origin(),
|
||||
@@ -2940,8 +2940,8 @@ mod tests {
|
||||
mgr.reload_from_cache_manager(&cache);
|
||||
|
||||
assert!(mgr.has_fetched_real_catalog());
|
||||
assert!(mgr.models().contains_key("grok-4.5"));
|
||||
assert!(mgr.models().contains_key("grok-4.3"));
|
||||
assert!(mgr.models().contains_key("kigi-4.5"));
|
||||
assert!(mgr.models().contains_key("kigi-4.3"));
|
||||
assert_eq!(mgr.inner.etag.read().as_deref(), Some("etag-ext"));
|
||||
}
|
||||
|
||||
@@ -3025,9 +3025,9 @@ mod tests {
|
||||
fn reload_from_disk_cache_skips_identical_catalog_and_adopts_etag() {
|
||||
let mgr = test_manager();
|
||||
let cfg = config::Config::default();
|
||||
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
|
||||
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
|
||||
mgr.apply_refresh_result(&cfg, Some(prefetched.clone()), Some("etag-a".into()));
|
||||
mgr.set_current_model_id(acp::ModelId::new("grok-4"));
|
||||
mgr.set_current_model_id(acp::ModelId::new("kigi-4"));
|
||||
|
||||
let tmp = tempfile::TempDir::new().unwrap();
|
||||
let cache = test_cache_manager(tmp.path());
|
||||
@@ -3043,7 +3043,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
mgr.current_model_id().0.as_ref(),
|
||||
"grok-4",
|
||||
"kigi-4",
|
||||
"identical catalog must not disturb the user's model"
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -3068,13 +3068,13 @@ mod tests {
|
||||
auth_method: Some(auth_method),
|
||||
origin: Some(mgr.cache_origin()),
|
||||
etag: Some("etag-stale".into()),
|
||||
models: make_prefetched(&["grok-stale"]),
|
||||
models: make_prefetched(&["kigi-stale"]),
|
||||
};
|
||||
cache.atomic_write(&stale);
|
||||
|
||||
mgr.reload_from_cache_manager(&cache);
|
||||
|
||||
assert!(!mgr.models().contains_key("grok-stale"));
|
||||
assert!(!mgr.models().contains_key("kigi-stale"));
|
||||
assert!(mgr.inner.etag.read().is_none());
|
||||
}
|
||||
|
||||
@@ -3093,7 +3093,7 @@ mod tests {
|
||||
CacheAuthMethod::Platforms
|
||||
};
|
||||
cache.persist(
|
||||
&make_prefetched(&["grok-other-auth"]),
|
||||
&make_prefetched(&["kigi-other-auth"]),
|
||||
Some("etag-x"),
|
||||
other,
|
||||
&mgr.cache_origin(),
|
||||
@@ -3101,7 +3101,7 @@ mod tests {
|
||||
|
||||
mgr.reload_from_cache_manager(&cache);
|
||||
|
||||
assert!(!mgr.models().contains_key("grok-other-auth"));
|
||||
assert!(!mgr.models().contains_key("kigi-other-auth"));
|
||||
}
|
||||
|
||||
/// A cache persisted by a process pointed at a *different backend* (env
|
||||
@@ -3117,7 +3117,7 @@ mod tests {
|
||||
let cache = test_cache_manager(tmp.path());
|
||||
let auth_method = mgr.inner.fetch_auth.read().cache_auth_method();
|
||||
cache.persist(
|
||||
&make_prefetched(&["grok-other-origin"]),
|
||||
&make_prefetched(&["kigi-other-origin"]),
|
||||
Some("etag-y"),
|
||||
auth_method,
|
||||
"http://127.0.0.1:49953/v1/models",
|
||||
@@ -3125,7 +3125,7 @@ mod tests {
|
||||
|
||||
mgr.reload_from_cache_manager(&cache);
|
||||
|
||||
assert!(!mgr.models().contains_key("grok-other-origin"));
|
||||
assert!(!mgr.models().contains_key("kigi-other-origin"));
|
||||
assert!(mgr.inner.etag.read().is_none());
|
||||
}
|
||||
|
||||
@@ -3144,13 +3144,13 @@ mod tests {
|
||||
auth_method: Some(auth_method),
|
||||
origin: None,
|
||||
etag: Some("etag-legacy".into()),
|
||||
models: make_prefetched(&["grok-legacy"]),
|
||||
models: make_prefetched(&["kigi-legacy"]),
|
||||
};
|
||||
cache.atomic_write(&legacy);
|
||||
|
||||
mgr.reload_from_cache_manager(&cache);
|
||||
|
||||
assert!(!mgr.models().contains_key("grok-legacy"));
|
||||
assert!(!mgr.models().contains_key("kigi-legacy"));
|
||||
}
|
||||
|
||||
// ── clear() resets has_fetched_real_catalog ──────────────────────
|
||||
@@ -3159,9 +3159,9 @@ mod tests {
|
||||
fn clear_resets_has_fetched_real_catalog() {
|
||||
let mgr = test_manager();
|
||||
let mut cfg = config::Config::default();
|
||||
cfg.models.default = Some("grok-3".to_string());
|
||||
cfg.models.default = Some("kigi-3".to_string());
|
||||
|
||||
let prefetched = make_prefetched(&["grok-3", "grok-4"]);
|
||||
let prefetched = make_prefetched(&["kigi-3", "kigi-4"]);
|
||||
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
|
||||
assert!(mgr.has_fetched_real_catalog());
|
||||
|
||||
@@ -3169,7 +3169,7 @@ mod tests {
|
||||
assert!(!mgr.has_fetched_real_catalog());
|
||||
|
||||
// New identity fetch — resolves default via reselect_default_model.
|
||||
let prefetched = make_prefetched(&["grok-4.5", "grok-4.3"]);
|
||||
let prefetched = make_prefetched(&["kigi-4.5", "kigi-4.3"]);
|
||||
mgr.apply_refresh_result(&cfg, Some(prefetched), None);
|
||||
let first_available = mgr.available().keys().next().unwrap().clone();
|
||||
assert_eq!(
|
||||
@@ -3610,25 +3610,25 @@ mod tests {
|
||||
#[test]
|
||||
fn build_prefetched_map_distinct_ids_same_slug() {
|
||||
let entries = vec![
|
||||
make_entry_config_with_id(Some("auto"), "grok-build", Some("Auto")),
|
||||
make_entry_config_with_id(Some("grok-build"), "grok-build", Some("Grok Build")),
|
||||
make_entry_config_with_id(Some("auto"), "kigi", Some("Auto")),
|
||||
make_entry_config_with_id(Some("kigi"), "kigi", Some("Kigi")),
|
||||
make_entry_config_with_id(
|
||||
Some("grok-composer-2.5-fast"),
|
||||
"grok-composer-2.5-fast",
|
||||
Some("Grok Fast"),
|
||||
Some("kigi-composer-2.5-fast"),
|
||||
"kigi-composer-2.5-fast",
|
||||
Some("Kigi Fast"),
|
||||
),
|
||||
];
|
||||
let map = build_prefetched_map(entries);
|
||||
|
||||
assert_eq!(map.len(), 3, "all three entries should survive");
|
||||
assert!(map.contains_key("auto"));
|
||||
assert!(map.contains_key("grok-build"));
|
||||
assert!(map.contains_key("grok-composer-2.5-fast"));
|
||||
assert!(map.contains_key("kigi"));
|
||||
assert!(map.contains_key("kigi-composer-2.5-fast"));
|
||||
assert_eq!(
|
||||
map["auto"].info.model, "grok-build",
|
||||
"auto entry should still route to grok-build"
|
||||
map["auto"].info.model, "kigi",
|
||||
"auto entry should still route to kigi"
|
||||
);
|
||||
assert_eq!(map["grok-build"].info.model, "grok-build");
|
||||
assert_eq!(map["kigi"].info.model, "kigi");
|
||||
}
|
||||
|
||||
/// No id field — falls back to model slug as key.
|
||||
@@ -3649,13 +3649,13 @@ mod tests {
|
||||
#[test]
|
||||
fn build_prefetched_map_duplicate_id_overwrites() {
|
||||
let entries = vec![
|
||||
make_entry_config_with_id(Some("grok-build"), "grok-build", Some("First")),
|
||||
make_entry_config_with_id(Some("grok-build"), "grok-build", Some("Second")),
|
||||
make_entry_config_with_id(Some("kigi"), "kigi", Some("First")),
|
||||
make_entry_config_with_id(Some("kigi"), "kigi", Some("Second")),
|
||||
];
|
||||
let map = build_prefetched_map(entries);
|
||||
|
||||
assert_eq!(map.len(), 1, "duplicate id: second overwrites first");
|
||||
assert_eq!(map["grok-build"].info.name.as_deref(), Some("Second"));
|
||||
assert_eq!(map["kigi"].info.name.as_deref(), Some("Second"));
|
||||
}
|
||||
|
||||
/// Regression: resolve_default_model must match by id before scanning
|
||||
@@ -3664,31 +3664,24 @@ mod tests {
|
||||
#[test]
|
||||
fn resolve_default_model_prefers_id_over_model_slug() {
|
||||
let mut catalog: IndexMap<String, ModelEntry> = IndexMap::new();
|
||||
catalog.insert(
|
||||
"auto-grok-build".to_string(),
|
||||
make_model_entry("grok-build"),
|
||||
);
|
||||
catalog.insert("grok-build".to_string(), make_model_entry("grok-build"));
|
||||
catalog.insert("auto-kigi".to_string(), make_model_entry("kigi"));
|
||||
catalog.insert("kigi".to_string(), make_model_entry("kigi"));
|
||||
|
||||
let mut cfg = config::Config::default();
|
||||
cfg.models.default = Some("grok-build".to_string());
|
||||
cfg.models.default = Some("kigi".to_string());
|
||||
|
||||
let (key, _, _) = resolve_default_model(&cfg, &catalog, true);
|
||||
assert_eq!(key, "grok-build", "must match id, not first slug hit");
|
||||
assert_eq!(key, "kigi", "must match id, not first slug hit");
|
||||
}
|
||||
|
||||
/// No id field — falls back to slug as key.
|
||||
#[test]
|
||||
fn build_prefetched_map_none_id_falls_back_to_slug() {
|
||||
let entries = vec![make_entry_config_with_id(
|
||||
None,
|
||||
"grok-build",
|
||||
Some("Grok Build"),
|
||||
)];
|
||||
let entries = vec![make_entry_config_with_id(None, "kigi", Some("Kigi"))];
|
||||
let map = build_prefetched_map(entries);
|
||||
|
||||
assert_eq!(map.len(), 1);
|
||||
assert!(map.contains_key("grok-build"));
|
||||
assert!(map.contains_key("kigi"));
|
||||
}
|
||||
|
||||
// ── persisted model id → catalog key (session resume) ─────────────
|
||||
@@ -3696,94 +3689,79 @@ mod tests {
|
||||
#[test]
|
||||
fn resolve_catalog_key_maps_routing_slug_to_config_key() {
|
||||
let mut models = IndexMap::new();
|
||||
models.insert(
|
||||
"enterprise-grok-build".to_string(),
|
||||
make_model_entry("grok-4.5"),
|
||||
);
|
||||
models.insert("grok-4.3".to_string(), make_model_entry("grok-4.3"));
|
||||
models.insert("enterprise-kigi".to_string(), make_model_entry("kigi-4.5"));
|
||||
models.insert("kigi-4.3".to_string(), make_model_entry("kigi-4.3"));
|
||||
|
||||
let persisted = acp::ModelId::new("grok-4.5");
|
||||
let persisted = acp::ModelId::new("kigi-4.5");
|
||||
let key = resolve_catalog_key(&models, &persisted).expect("slug must resolve");
|
||||
assert_eq!(key.0.as_ref(), "enterprise-grok-build");
|
||||
assert_eq!(key.0.as_ref(), "enterprise-kigi");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_catalog_key_prefers_exact_key_match() {
|
||||
let mut models = IndexMap::new();
|
||||
models.insert("grok-4.5".to_string(), make_model_entry("grok-4.5"));
|
||||
models.insert("kigi-4.5".to_string(), make_model_entry("kigi-4.5"));
|
||||
|
||||
let persisted = acp::ModelId::new("grok-4.5");
|
||||
let persisted = acp::ModelId::new("kigi-4.5");
|
||||
let key = resolve_catalog_key(&models, &persisted).expect("exact key must resolve");
|
||||
assert_eq!(key.0.as_ref(), "grok-4.5");
|
||||
assert_eq!(key.0.as_ref(), "kigi-4.5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_catalog_key_last_slug_match_wins() {
|
||||
let mut models = IndexMap::new();
|
||||
models.insert(
|
||||
"default-grok-build".to_string(),
|
||||
make_model_entry("grok-4.5"),
|
||||
);
|
||||
models.insert("user-grok-build".to_string(), make_model_entry("grok-4.5"));
|
||||
models.insert("default-kigi".to_string(), make_model_entry("kigi-4.5"));
|
||||
models.insert("user-kigi".to_string(), make_model_entry("kigi-4.5"));
|
||||
|
||||
let persisted = acp::ModelId::new("grok-4.5");
|
||||
let persisted = acp::ModelId::new("kigi-4.5");
|
||||
let key = resolve_catalog_key(&models, &persisted).expect("slug must resolve");
|
||||
assert_eq!(key.0.as_ref(), "user-grok-build");
|
||||
assert_eq!(key.0.as_ref(), "user-kigi");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selectable_catalog_key_for_persisted_none_when_resolved_not_available() {
|
||||
let mut models = IndexMap::new();
|
||||
models.insert(
|
||||
"enterprise-grok-build".to_string(),
|
||||
make_model_entry("grok-4.5"),
|
||||
);
|
||||
models.insert("enterprise-kigi".to_string(), make_model_entry("kigi-4.5"));
|
||||
|
||||
let available: IndexMap<_, _> = IndexMap::new();
|
||||
let persisted = acp::ModelId::new("grok-4.5");
|
||||
let persisted = acp::ModelId::new("kigi-4.5");
|
||||
assert!(selectable_catalog_key_for_persisted(&models, &available, &persisted).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selectable_prefers_available_identity_over_non_selectable_exact_key() {
|
||||
let mut models = IndexMap::new();
|
||||
models.insert("grok-build".to_string(), make_model_entry("grok-build"));
|
||||
models.insert(
|
||||
"enterprise-grok-build".to_string(),
|
||||
make_model_entry("grok-build"),
|
||||
);
|
||||
models.insert("grok-4.3".to_string(), make_model_entry("grok-4.3"));
|
||||
models.insert("kigi".to_string(), make_model_entry("kigi"));
|
||||
models.insert("enterprise-kigi".to_string(), make_model_entry("kigi"));
|
||||
models.insert("kigi-4.3".to_string(), make_model_entry("kigi-4.3"));
|
||||
|
||||
let available = test_available_keys(&["enterprise-grok-build", "grok-4.3"]);
|
||||
let available = test_available_keys(&["enterprise-kigi", "kigi-4.3"]);
|
||||
|
||||
let persisted = acp::ModelId::new("grok-build");
|
||||
let persisted = acp::ModelId::new("kigi");
|
||||
assert_eq!(
|
||||
resolve_catalog_key(&models, &persisted)
|
||||
.expect("exact key exists")
|
||||
.0
|
||||
.as_ref(),
|
||||
"grok-build"
|
||||
"kigi"
|
||||
);
|
||||
let key = selectable_catalog_key_for_persisted(&models, &available, &persisted)
|
||||
.expect("must resolve to selectable section");
|
||||
assert_eq!(key.0.as_ref(), "enterprise-grok-build");
|
||||
assert_eq!(key.0.as_ref(), "enterprise-kigi");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selectable_matches_routing_slug_when_no_exact_key() {
|
||||
let mut models = IndexMap::new();
|
||||
models.insert(
|
||||
"enterprise-grok-build".to_string(),
|
||||
make_model_entry("grok-build"),
|
||||
);
|
||||
models.insert("grok-4.3".to_string(), make_model_entry("grok-4.3"));
|
||||
models.insert("enterprise-kigi".to_string(), make_model_entry("kigi"));
|
||||
models.insert("kigi-4.3".to_string(), make_model_entry("kigi-4.3"));
|
||||
|
||||
let available = test_available_keys(&["enterprise-grok-build", "grok-4.3"]);
|
||||
let available = test_available_keys(&["enterprise-kigi", "kigi-4.3"]);
|
||||
|
||||
let persisted = acp::ModelId::new("grok-build");
|
||||
let persisted = acp::ModelId::new("kigi");
|
||||
let key = selectable_catalog_key_for_persisted(&models, &available, &persisted)
|
||||
.expect("slug must resolve to selectable key");
|
||||
assert_eq!(key.0.as_ref(), "enterprise-grok-build");
|
||||
assert_eq!(key.0.as_ref(), "enterprise-kigi");
|
||||
}
|
||||
|
||||
/// A persisted *selectable* catalog key binds to itself even when a later
|
||||
@@ -3791,15 +3769,15 @@ mod tests {
|
||||
#[test]
|
||||
fn selectable_prefers_exact_key_over_later_slug_match() {
|
||||
let mut models = IndexMap::new();
|
||||
models.insert("grok-build".to_string(), make_model_entry("grok-4.5"));
|
||||
models.insert("other".to_string(), make_model_entry("grok-build"));
|
||||
models.insert("kigi".to_string(), make_model_entry("kigi-4.5"));
|
||||
models.insert("other".to_string(), make_model_entry("kigi"));
|
||||
|
||||
let available = test_available_keys(&["grok-build", "other"]);
|
||||
let available = test_available_keys(&["kigi", "other"]);
|
||||
|
||||
let persisted = acp::ModelId::new("grok-build");
|
||||
let persisted = acp::ModelId::new("kigi");
|
||||
let key = selectable_catalog_key_for_persisted(&models, &available, &persisted)
|
||||
.expect("exact selectable key must win");
|
||||
assert_eq!(key.0.as_ref(), "grok-build");
|
||||
assert_eq!(key.0.as_ref(), "kigi");
|
||||
}
|
||||
|
||||
fn test_available_keys(keys: &[&str]) -> IndexMap<acp::ModelId, acp::ModelInfo> {
|
||||
|
||||
@@ -657,13 +657,13 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_openai_format_uses_id_field() {
|
||||
let value = serde_json::json!(
|
||||
{ "id" : "grok-3", "object" : "model", "owned_by" : "xai", "context_window" :
|
||||
{ "id" : "kigi-3", "object" : "model", "owned_by" : "xai", "context_window" :
|
||||
131072 }
|
||||
);
|
||||
let result = parse_remote_model_value(&value, "https://api.x.ai/v1").unwrap();
|
||||
assert_eq!(result.model, "grok-3");
|
||||
assert_eq!(result.base_url, "https://api.x.ai/v1");
|
||||
assert_eq!(result.name.as_deref(), Some("grok-3"));
|
||||
let result = parse_remote_model_value(&value, "https://byok.example/v1").unwrap();
|
||||
assert_eq!(result.model, "kigi-3");
|
||||
assert_eq!(result.base_url, "https://byok.example/v1");
|
||||
assert_eq!(result.name.as_deref(), Some("kigi-3"));
|
||||
}
|
||||
#[test]
|
||||
fn parse_model_field_takes_priority_over_id() {
|
||||
@@ -753,14 +753,14 @@ mod tests {
|
||||
fn parse_reads_reasoning_effort_fields() {
|
||||
use kigi_sampling_types::ReasoningEffort;
|
||||
let value = serde_json::json!(
|
||||
{ "model" : "grok-4.5", "context_window" : 1_000_000,
|
||||
{ "model" : "kigi-4.5", "context_window" : 1_000_000,
|
||||
"supports_reasoning_effort" : true, "reasoning_effort" : "high" }
|
||||
);
|
||||
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
|
||||
assert!(result.supports_reasoning_effort);
|
||||
assert_eq!(result.reasoning_effort, Some(ReasoningEffort::High));
|
||||
let value = serde_json::json!(
|
||||
{ "model" : "grok-4.5", "contextWindow" : 1_000_000,
|
||||
{ "model" : "kigi-4.5", "contextWindow" : 1_000_000,
|
||||
"supportsReasoningEffort" : true, "reasoningEffort" : "xhigh" }
|
||||
);
|
||||
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
|
||||
@@ -775,7 +775,7 @@ mod tests {
|
||||
fn parse_reads_reasoning_efforts_list() {
|
||||
use kigi_sampling_types::ReasoningEffort;
|
||||
let value = serde_json::json!(
|
||||
{ "model" : "grok-4.5", "context_window" : 1_000_000, "reasoning_efforts" :
|
||||
{ "model" : "kigi-4.5", "context_window" : 1_000_000, "reasoning_efforts" :
|
||||
[{ "id" : "deep", "value" : "xhigh", "label" : "Deep" }, { "value" :
|
||||
"quantum" }, "low",] }
|
||||
);
|
||||
@@ -819,7 +819,7 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_remote_model_value_no_laziness_detector_block_yields_default() {
|
||||
let value = serde_json::json!(
|
||||
{ "model" : "grok-4", "context_window" : 256_000, }
|
||||
{ "model" : "kigi-4", "context_window" : 256_000, }
|
||||
);
|
||||
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
|
||||
assert_eq!(
|
||||
@@ -830,7 +830,7 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_remote_model_value_parses_camelcase_key() {
|
||||
let value = serde_json::json!(
|
||||
{ "model" : "grok-4", "context_window" : 256_000, "lazinessDetector" : {
|
||||
{ "model" : "kigi-4", "context_window" : 256_000, "lazinessDetector" : {
|
||||
"enabled" : true, "max_nudges_per_session" : 2, "idle_threshold_ms" : 12_000,
|
||||
"min_confidence" : 0.75, }, }
|
||||
);
|
||||
@@ -847,7 +847,7 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_remote_model_value_parses_snake_case_laziness_detector() {
|
||||
let value = serde_json::json!(
|
||||
{ "model" : "grok-4", "context_window" : 256_000, "laziness_detector" : {
|
||||
{ "model" : "kigi-4", "context_window" : 256_000, "laziness_detector" : {
|
||||
"enabled" : true, "max_nudges_per_session" : 3, "idle_threshold_ms" : 8_000,
|
||||
"min_confidence" : 0.6, }, }
|
||||
);
|
||||
@@ -864,7 +864,7 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_remote_model_value_parses_meta_laziness_detector() {
|
||||
let value = serde_json::json!(
|
||||
{ "model" : "grok-4", "context_window" : 256_000, "_meta" : {
|
||||
{ "model" : "kigi-4", "context_window" : 256_000, "_meta" : {
|
||||
"lazinessDetector" : { "enabled" : true, "max_nudges_per_session" : 1,
|
||||
"idle_threshold_ms" : 15_000, "min_confidence" : 0.9, }, }, }
|
||||
);
|
||||
@@ -881,7 +881,7 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_remote_model_value_partial_block_uses_field_defaults() {
|
||||
let value = serde_json::json!(
|
||||
{ "model" : "grok-4", "context_window" : 256_000, "lazinessDetector" : {
|
||||
{ "model" : "kigi-4", "context_window" : 256_000, "lazinessDetector" : {
|
||||
"enabled" : true, }, }
|
||||
);
|
||||
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
|
||||
@@ -897,7 +897,7 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_remote_model_value_malformed_block_falls_back_to_default() {
|
||||
let value = serde_json::json!(
|
||||
{ "model" : "grok-4", "context_window" : 256_000, "lazinessDetector" : {
|
||||
{ "model" : "kigi-4", "context_window" : 256_000, "lazinessDetector" : {
|
||||
"enabled" : true, "max_nudges_per_session" : "abc", }, }
|
||||
);
|
||||
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
|
||||
@@ -909,7 +909,7 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_remote_model_value_non_object_value_falls_back_to_default() {
|
||||
let value = serde_json::json!(
|
||||
{ "model" : "grok-4", "context_window" : 256_000, "lazinessDetector" :
|
||||
{ "model" : "kigi-4", "context_window" : 256_000, "lazinessDetector" :
|
||||
"not-an-object", }
|
||||
);
|
||||
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
|
||||
@@ -921,7 +921,7 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_remote_model_value_top_level_camelcase_wins_over_snake_case() {
|
||||
let value = serde_json::json!(
|
||||
{ "model" : "grok-4", "context_window" : 256_000, "lazinessDetector" : {
|
||||
{ "model" : "kigi-4", "context_window" : 256_000, "lazinessDetector" : {
|
||||
"enabled" : true, "max_nudges_per_session" : 7, }, "laziness_detector" : {
|
||||
"enabled" : false, "max_nudges_per_session" : 99, }, }
|
||||
);
|
||||
@@ -942,7 +942,7 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_remote_model_value_parses_include_reasoning_under_camelcase_wrapper() {
|
||||
let value = serde_json::json!(
|
||||
{ "model" : "grok-4", "context_window" : 256_000, "lazinessDetector" : {
|
||||
{ "model" : "kigi-4", "context_window" : 256_000, "lazinessDetector" : {
|
||||
"enabled" : true, "include_reasoning" : false, }, }
|
||||
);
|
||||
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
|
||||
@@ -951,7 +951,7 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_remote_model_value_parses_include_reasoning_under_snake_case_wrapper() {
|
||||
let value = serde_json::json!(
|
||||
{ "model" : "grok-4", "context_window" : 256_000, "laziness_detector" : {
|
||||
{ "model" : "kigi-4", "context_window" : 256_000, "laziness_detector" : {
|
||||
"enabled" : true, "include_reasoning" : true, }, }
|
||||
);
|
||||
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
|
||||
@@ -960,7 +960,7 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_remote_model_value_omitted_include_reasoning_defaults_to_none() {
|
||||
let value = serde_json::json!(
|
||||
{ "model" : "grok-4", "context_window" : 256_000, "lazinessDetector" : {
|
||||
{ "model" : "kigi-4", "context_window" : 256_000, "lazinessDetector" : {
|
||||
"enabled" : true, "max_nudges_per_session" : 2, }, }
|
||||
);
|
||||
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
|
||||
@@ -972,7 +972,7 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_remote_model_value_top_level_wins_over_meta() {
|
||||
let value = serde_json::json!(
|
||||
{ "model" : "grok-4", "context_window" : 256_000, "lazinessDetector" : {
|
||||
{ "model" : "kigi-4", "context_window" : 256_000, "lazinessDetector" : {
|
||||
"enabled" : true, "max_nudges_per_session" : 5, }, "_meta" : {
|
||||
"lazinessDetector" : { "enabled" : false, "max_nudges_per_session" : 99, },
|
||||
}, }
|
||||
@@ -990,19 +990,19 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_reads_show_model_fingerprint_field() {
|
||||
let value = serde_json::json!(
|
||||
{ "model" : "grok-build", "context_window" : 256_000,
|
||||
{ "model" : "kigi", "context_window" : 256_000,
|
||||
"show_model_fingerprint" : true }
|
||||
);
|
||||
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
|
||||
assert!(result.show_model_fingerprint);
|
||||
let value = serde_json::json!(
|
||||
{ "model" : "grok-build", "contextWindow" : 256_000, "showModelFingerprint" :
|
||||
{ "model" : "kigi", "contextWindow" : 256_000, "showModelFingerprint" :
|
||||
true }
|
||||
);
|
||||
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
|
||||
assert!(result.show_model_fingerprint);
|
||||
let value = serde_json::json!(
|
||||
{ "model" : "grok-build", "context_window" : 256_000, "_meta" : {
|
||||
{ "model" : "kigi", "context_window" : 256_000, "_meta" : {
|
||||
"showModelFingerprint" : true } }
|
||||
);
|
||||
let result = parse_remote_model_value(&value, "https://default.url").unwrap();
|
||||
@@ -1087,10 +1087,13 @@ mod tests {
|
||||
fn list_url_derived_from_base_url() {
|
||||
let ep = endpoints(
|
||||
"https://proxy.kigi.com/v1",
|
||||
Some("https://api.x.ai/v1"),
|
||||
Some("https://byok.example/v1"),
|
||||
None,
|
||||
);
|
||||
assert_eq!(ep.resolve_models_list_url(), "https://api.x.ai/v1/models");
|
||||
assert_eq!(
|
||||
ep.resolve_models_list_url(),
|
||||
"https://byok.example/v1/models"
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn list_url_explicit_overrides_derivation() {
|
||||
|
||||
@@ -65,10 +65,10 @@ impl acp::Agent for MvpAgent {
|
||||
}
|
||||
if client_type == ClientType::Generic {
|
||||
match client_identifier.as_deref() {
|
||||
Some("grok-web") => client_type = ClientType::GrokWeb,
|
||||
Some("kigi-web") => client_type = ClientType::KigiWeb,
|
||||
Some("nebula") => client_type = ClientType::Nebula,
|
||||
Some("grok-code-extension") => client_type = ClientType::Extension,
|
||||
Some("grok-desktop") => client_type = ClientType::Desktop,
|
||||
Some("kigi-code-extension") => client_type = ClientType::Extension,
|
||||
Some("kigi-desktop") => client_type = ClientType::Desktop,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ impl acp::Agent for MvpAgent {
|
||||
code_nav_enabled, client_type = ? client_type, event =
|
||||
"code_nav_capability_parsed",
|
||||
"code-nav capability initialized from initialize request; \
|
||||
index will start lazily on first x.ai/code/* request if eligible"
|
||||
index will start lazily on first kigi/code/* request if eligible"
|
||||
);
|
||||
let interactive_trust_client = Self::parse_interactive_trust_capability(
|
||||
&arguments,
|
||||
@@ -279,7 +279,7 @@ impl acp::Agent for MvpAgent {
|
||||
.load_session(true)
|
||||
.meta(
|
||||
serde_json::json!(
|
||||
{ "x.ai/fs_notify" : true, "x.ai/hooks" : { "blockingEvents"
|
||||
{ "kigi/fs_notify" : true, "kigi/hooks" : { "blockingEvents"
|
||||
: [kigi_hooks::event::HookEventName::PreToolUse],
|
||||
"decisions" : ["deny"], }, }
|
||||
)
|
||||
@@ -297,7 +297,7 @@ impl acp::Agent for MvpAgent {
|
||||
.meta({
|
||||
let metadata = parse_json_object_env("KIGI_AGENT_METADATA");
|
||||
serde_json::json!(
|
||||
{ "grokShell" : true, "defaultAuthMethodId" :
|
||||
{ "kigiShell" : true, "defaultAuthMethodId" :
|
||||
default_auth_method_id_wire, (kigi_mcp::wire::MCP_SDK) :
|
||||
true, (SESSION_PLUGIN_DIRS_CAPABILITY_KEY) : true,
|
||||
"currentWorkingDirectory" : current_working_directory
|
||||
@@ -372,7 +372,7 @@ impl acp::Agent for MvpAgent {
|
||||
return self
|
||||
.authenticate(
|
||||
acp::AuthenticateRequest::new(
|
||||
acp::AuthMethodId::new(auth_method::KIGI_COM_METHOD_ID),
|
||||
acp::AuthMethodId::new(auth_method::KIMI_CODE_METHOD_ID),
|
||||
)
|
||||
.meta(arguments.meta),
|
||||
)
|
||||
@@ -425,7 +425,7 @@ impl acp::Agent for MvpAgent {
|
||||
emit_login_span(true, "cached_token", uid.as_deref(), None);
|
||||
Ok(self.auth_response_with_meta())
|
||||
}
|
||||
auth_method::KIGI_COM_METHOD_ID => {
|
||||
auth_method::KIMI_CODE_METHOD_ID => {
|
||||
let kimi_ctx = self.auth_manager.kimi_code_config().clone();
|
||||
let auth_meta = AuthRequestMeta::from_json(arguments.meta.as_ref());
|
||||
tracing::info!(
|
||||
@@ -488,10 +488,10 @@ impl acp::Agent for MvpAgent {
|
||||
let mut sampling_config = self.sampling_config.borrow_mut();
|
||||
sampling_config.api_key = Some(auth.key.clone());
|
||||
tracing::debug!(
|
||||
"auth: grok.com/oidc handler set api_key (SessionToken)"
|
||||
"auth: kimi.com/oidc handler set api_key (SessionToken)"
|
||||
);
|
||||
kigi_log::unified_log::debug(
|
||||
"auth: grok.com/oidc handler set api_key (SessionToken)",
|
||||
"auth: kimi.com/oidc handler set api_key (SessionToken)",
|
||||
None,
|
||||
None,
|
||||
);
|
||||
@@ -827,7 +827,7 @@ impl acp::Agent for MvpAgent {
|
||||
Some(serde_json::json!({ "cwd" : cwd.as_str() })),
|
||||
);
|
||||
let models = if is_chat_kind {
|
||||
// The grok.com chat-mode model picker was removed with the xAI
|
||||
// The kimi.com chat-mode model picker was removed with the xAI
|
||||
// proxy; a chat-kind session has no managed catalog to offer.
|
||||
chat_new_session_model_state(
|
||||
acp::SessionModelState::new(acp::ModelId::from(String::new()), Vec::new()),
|
||||
@@ -846,8 +846,8 @@ impl acp::Agent for MvpAgent {
|
||||
feedback_enabled, }
|
||||
);
|
||||
if let Some(obj) = meta.as_object_mut() {
|
||||
obj.insert("x.ai/sessionConfig".to_string(), session_config_value);
|
||||
obj.insert("x.ai/sessionDetail".to_string(), session_detail_value);
|
||||
obj.insert("kigi/sessionConfig".to_string(), session_config_value);
|
||||
obj.insert("kigi/sessionDetail".to_string(), session_detail_value);
|
||||
}
|
||||
Ok(
|
||||
acp::NewSessionResponse::new(session_id)
|
||||
@@ -874,12 +874,12 @@ impl acp::Agent for MvpAgent {
|
||||
let persist_data = arguments
|
||||
.meta
|
||||
.as_ref()
|
||||
.and_then(|m| m.get("x.ai/persist"))
|
||||
.and_then(|m| m.get("kigi/persist"))
|
||||
.cloned();
|
||||
let target_client_id = arguments
|
||||
.meta
|
||||
.as_ref()
|
||||
.and_then(|m| m.get("x.ai/leaderClientId"))
|
||||
.and_then(|m| m.get("kigi/leaderClientId"))
|
||||
.cloned();
|
||||
let acp::LoadSessionRequest {
|
||||
session_id,
|
||||
@@ -1010,7 +1010,7 @@ impl acp::Agent for MvpAgent {
|
||||
);
|
||||
let restore_code_requested = request_meta
|
||||
.as_ref()
|
||||
.and_then(|m| m.get("x.ai/restore_code"))
|
||||
.and_then(|m| m.get("kigi/restore_code"))
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(self.restore_code);
|
||||
let registry_client_for_restore = self.session_registry_client();
|
||||
@@ -1030,7 +1030,7 @@ impl acp::Agent for MvpAgent {
|
||||
target : kigi_workspace::session::git::RESTORE_CODE_LOG, session_id =
|
||||
% session_id.0, supplied_cwd = % cwd.as_str(), persisted_cwd = % summary
|
||||
.info.cwd, target_sha = % target_sha,
|
||||
"restore_code: skipping session HEAD checkout — supplied cwd is neither a grok worktree nor the session's persisted cwd (refusing to detach the source repo)"
|
||||
"restore_code: skipping session HEAD checkout — supplied cwd is neither a kigi worktree nor the session's persisted cwd (refusing to detach the source repo)"
|
||||
);
|
||||
kigi_log::unified_log::warn(
|
||||
"restore_code: skipped session HEAD checkout (unsafe cwd)",
|
||||
@@ -1075,7 +1075,7 @@ impl acp::Agent for MvpAgent {
|
||||
let load_envrc = {
|
||||
let skip_envrc = request_meta
|
||||
.as_ref()
|
||||
.and_then(|m| m.get("x.ai/skip_envrc"))
|
||||
.and_then(|m| m.get("kigi/skip_envrc"))
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
if skip_envrc {
|
||||
@@ -1156,7 +1156,7 @@ impl acp::Agent for MvpAgent {
|
||||
);
|
||||
let prompt_display_cwd = request_meta
|
||||
.as_ref()
|
||||
.and_then(|m| m.get("x.ai/display_cwd"))
|
||||
.and_then(|m| m.get("kigi/display_cwd"))
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string())
|
||||
.or_else(|| summary.prompt_display_cwd.clone());
|
||||
@@ -1462,7 +1462,7 @@ impl acp::Agent for MvpAgent {
|
||||
let mut response_meta_map = serde_json::Map::new();
|
||||
response_meta_map.insert("sessionId".to_string(), serde_json::json!(session_id));
|
||||
if let Some(persist) = persist_data {
|
||||
response_meta_map.insert("x.ai/persist".to_string(), persist);
|
||||
response_meta_map.insert("kigi/persist".to_string(), persist);
|
||||
}
|
||||
let session_cwd = self
|
||||
.sessions
|
||||
@@ -1517,7 +1517,7 @@ impl acp::Agent for MvpAgent {
|
||||
{
|
||||
response_meta_map
|
||||
.insert(
|
||||
"x.ai/runningPromptId".to_string(),
|
||||
"kigi/runningPromptId".to_string(),
|
||||
serde_json::json!(running_prompt_id),
|
||||
);
|
||||
}
|
||||
@@ -1529,8 +1529,8 @@ impl acp::Agent for MvpAgent {
|
||||
summary.display_title_opt(),
|
||||
&model_state,
|
||||
);
|
||||
response_meta_map.insert("x.ai/sessionConfig".to_string(), session_config_value);
|
||||
response_meta_map.insert("x.ai/sessionDetail".to_string(), session_detail_value);
|
||||
response_meta_map.insert("kigi/sessionConfig".to_string(), session_config_value);
|
||||
response_meta_map.insert("kigi/sessionDetail".to_string(), session_detail_value);
|
||||
let response_meta = serde_json::Value::Object(response_meta_map);
|
||||
kigi_log::unified_log::info(
|
||||
"session loaded",
|
||||
@@ -1846,7 +1846,7 @@ impl acp::Agent for MvpAgent {
|
||||
self.gateway
|
||||
.forward_fire_and_forget(
|
||||
acp::ExtNotification::new(
|
||||
"x.ai/session/prompt_complete",
|
||||
"kigi/session/prompt_complete",
|
||||
params.into(),
|
||||
),
|
||||
);
|
||||
@@ -2045,116 +2045,116 @@ impl acp::Agent for MvpAgent {
|
||||
let mut backend_no_bridge_err: Option<acp::Error> = None;
|
||||
let method = args.method.clone();
|
||||
let result = match method.as_ref() {
|
||||
"x.ai/getApiKey" | "x.ai/setApiKey" => {
|
||||
"kigi/getApiKey" | "kigi/setApiKey" => {
|
||||
crate::extensions::auth::handle(self, &args).await
|
||||
}
|
||||
"x.ai/session/info" | "x.ai/session/close" | "x.ai/session/list"
|
||||
| "x.ai/sessions/list" => {
|
||||
"kigi/session/info" | "kigi/session/close" | "kigi/session/list"
|
||||
| "kigi/sessions/list" => {
|
||||
crate::agent::handlers::session::handle(self, &args).await
|
||||
}
|
||||
"x.ai/session/updates" => {
|
||||
"kigi/session/updates" => {
|
||||
crate::extensions::session_updates::handle(&args, &self.gateway).await
|
||||
}
|
||||
"x.ai/session/load_history" => {
|
||||
"kigi/session/load_history" => {
|
||||
crate::extensions::chat_conversation_history::handle(self, &args).await
|
||||
}
|
||||
"x.ai/session/search" => {
|
||||
"kigi/session/search" => {
|
||||
crate::extensions::session_search::handle(&args).await
|
||||
}
|
||||
"x.ai/session/resolve_local_for_worktree_resume"
|
||||
| "x.ai/session/rehydrate" => {
|
||||
"kigi/session/resolve_local_for_worktree_resume"
|
||||
| "kigi/session/rehydrate" => {
|
||||
let ops = self.resolve_workspace_ops()?;
|
||||
crate::extensions::worktree::handle(self, &ops, &args).await
|
||||
}
|
||||
"x.ai/session/rename" | "x.ai/session/delete"
|
||||
| "x.ai/session/update_mcp_servers" | "x.ai/session/fork"
|
||||
| "x.ai/internal/reload_all_mcp_servers"
|
||||
| "x.ai/internal/reload_project_mcp_servers" | "x.ai/internal/reload_skills"
|
||||
| "x.ai/internal/reload_models" | "x.ai/internal/reload_models_cache"
|
||||
| "x.ai/plugins/reload"
|
||||
| "x.ai/commands/list" => {
|
||||
"kigi/session/rename" | "kigi/session/delete"
|
||||
| "kigi/session/update_mcp_servers" | "kigi/session/fork"
|
||||
| "kigi/internal/reload_all_mcp_servers"
|
||||
| "kigi/internal/reload_project_mcp_servers" | "kigi/internal/reload_skills"
|
||||
| "kigi/internal/reload_models" | "kigi/internal/reload_models_cache"
|
||||
| "kigi/plugins/reload"
|
||||
| "kigi/commands/list" => {
|
||||
crate::extensions::session_admin::handle(self, &args).await
|
||||
}
|
||||
"x.ai/session/repair" => crate::extensions::repair::handle(self, &args).await,
|
||||
"x.ai/billing" => crate::extensions::billing::handle(self, &args).await,
|
||||
"x.ai/memory/flush" | "x.ai/memory/rewrite" => {
|
||||
"kigi/session/repair" => crate::extensions::repair::handle(self, &args).await,
|
||||
"kigi/billing" => crate::extensions::billing::handle(self, &args).await,
|
||||
"kigi/memory/flush" | "kigi/memory/rewrite" => {
|
||||
crate::extensions::memory::handle(self, &args).await
|
||||
}
|
||||
"x.ai/skills/refresh-baseline" => {
|
||||
"kigi/skills/refresh-baseline" => {
|
||||
self.refresh_skill_baseline_for_all_sessions();
|
||||
crate::extensions::to_ext_response(
|
||||
Ok(serde_json::json!({ "ok" : true })),
|
||||
)
|
||||
}
|
||||
"x.ai/interject" => crate::extensions::interject::handle(self, &args).await,
|
||||
"x.ai/feedback" | "x.ai/feedback/dismiss" | "x.ai/btw" => {
|
||||
"kigi/interject" => crate::extensions::interject::handle(self, &args).await,
|
||||
"kigi/feedback" | "kigi/feedback/dismiss" | "kigi/btw" => {
|
||||
crate::extensions::feedback::handle(self, &args).await
|
||||
}
|
||||
"x.ai/recap" => crate::extensions::recap::handle(self, &args).await,
|
||||
"x.ai/rollout/survey" => {
|
||||
"kigi/recap" => crate::extensions::recap::handle(self, &args).await,
|
||||
"kigi/rollout/survey" => {
|
||||
crate::extensions::rollout::handle(self, &args).await
|
||||
}
|
||||
"x.ai/prompt_history" => {
|
||||
"kigi/prompt_history" => {
|
||||
crate::extensions::prompt_history::handle(self, &args).await
|
||||
}
|
||||
"x.ai/suggest" => crate::extensions::suggest::handle(self, &args).await,
|
||||
"x.ai/suggestPrompt" => crate::extensions::suggest::handle(self, &args).await,
|
||||
s if s.starts_with("x.ai/auth/") => {
|
||||
"kigi/suggest" => crate::extensions::suggest::handle(self, &args).await,
|
||||
"kigi/suggestPrompt" => crate::extensions::suggest::handle(self, &args).await,
|
||||
s if s.starts_with("kigi/auth/") => {
|
||||
crate::extensions::auth::handle(self, &args).await
|
||||
}
|
||||
s if s.starts_with("x.ai/session_summaries/") => {
|
||||
s if s.starts_with("kigi/session_summaries/") => {
|
||||
crate::agent::handlers::session::handle(self, &args).await
|
||||
}
|
||||
s if s.starts_with("x.ai/git/worktree/") => {
|
||||
s if s.starts_with("kigi/git/worktree/") => {
|
||||
let ops = self.resolve_workspace_ops()?;
|
||||
crate::extensions::worktree::handle(self, &ops, &args).await
|
||||
}
|
||||
s if s.starts_with("x.ai/git/") => {
|
||||
s if s.starts_with("kigi/git/") => {
|
||||
let ops = self.resolve_workspace_ops()?;
|
||||
crate::extensions::git::handle(self, &ops, &args).await
|
||||
}
|
||||
s if s.starts_with("x.ai/compact_conversation") => {
|
||||
s if s.starts_with("kigi/compact_conversation") => {
|
||||
crate::extensions::memory::handle(self, &args).await
|
||||
}
|
||||
s if s.starts_with("x.ai/plugins/") => {
|
||||
s if s.starts_with("kigi/plugins/") => {
|
||||
crate::extensions::plugins::handle(self, &args).await
|
||||
}
|
||||
s if s.starts_with("x.ai/hooks/") => {
|
||||
s if s.starts_with("kigi/hooks/") => {
|
||||
crate::extensions::hooks::handle(self, &args).await
|
||||
}
|
||||
s if s.starts_with("x.ai/hunk-tracker/") => {
|
||||
s if s.starts_with("kigi/hunk-tracker/") => {
|
||||
let ops = self.resolve_workspace_ops()?;
|
||||
crate::extensions::hunk_tracker::handle(self, &ops, &args).await
|
||||
}
|
||||
s if s.starts_with("x.ai/pr/") => {
|
||||
s if s.starts_with("kigi/pr/") => {
|
||||
crate::extensions::pr::handle(self, &args).await
|
||||
}
|
||||
s if s.starts_with(crate::extensions::mcp::mcp_methods::PREFIX) => {
|
||||
crate::extensions::mcp::handle(self, &args).await
|
||||
}
|
||||
s if s.starts_with("x.ai/task/") => {
|
||||
s if s.starts_with("kigi/task/") => {
|
||||
crate::extensions::task::handle(self, &args).await
|
||||
}
|
||||
s if s.starts_with("x.ai/scheduler/") => {
|
||||
s if s.starts_with("kigi/scheduler/") => {
|
||||
crate::extensions::task::handle_scheduler(self, &args).await
|
||||
}
|
||||
s if s.starts_with("x.ai/subagent/") => {
|
||||
s if s.starts_with("kigi/subagent/") => {
|
||||
crate::extensions::task::handle_subagent(self, &args).await
|
||||
}
|
||||
s if s.starts_with("x.ai/terminal/") => {
|
||||
s if s.starts_with("kigi/terminal/") => {
|
||||
crate::extensions::terminal::handle(self, &args).await
|
||||
}
|
||||
s if crate::extensions::fs::is_fs_method(s) => {
|
||||
crate::extensions::fs::handle(self, &args).await
|
||||
}
|
||||
s if s.starts_with("x.ai/search/") => {
|
||||
s if s.starts_with("kigi/search/") => {
|
||||
crate::extensions::search::handle(self, &args).await
|
||||
}
|
||||
s if s.starts_with("x.ai/code/") => {
|
||||
s if s.starts_with("kigi/code/") => {
|
||||
let ops = self.resolve_workspace_ops()?;
|
||||
crate::extensions::code_nav::handle(self, &ops, &args).await
|
||||
}
|
||||
s if s.starts_with("x.ai/skills/") => {
|
||||
s if s.starts_with("kigi/skills/") => {
|
||||
let compat = self.cfg.borrow().compat_resolved;
|
||||
crate::extensions::skills::handle(
|
||||
&args,
|
||||
@@ -2163,13 +2163,13 @@ impl acp::Agent for MvpAgent {
|
||||
)
|
||||
.await
|
||||
}
|
||||
s if s.starts_with("x.ai/review") => {
|
||||
s if s.starts_with("kigi/review") => {
|
||||
crate::extensions::feedback::handle(self, &args).await
|
||||
}
|
||||
s if s.starts_with("x.ai/debug/") => {
|
||||
s if s.starts_with("kigi/debug/") => {
|
||||
crate::extensions::debug::handle(self, &args).await
|
||||
}
|
||||
s if s.starts_with("x.ai/rewind") => {
|
||||
s if s.starts_with("kigi/rewind") => {
|
||||
crate::extensions::rewind::handle(self, &args).await
|
||||
}
|
||||
other => {
|
||||
@@ -2193,7 +2193,7 @@ impl acp::Agent for MvpAgent {
|
||||
args: acp::ExtNotification,
|
||||
) -> Result<(), acp::Error> {
|
||||
tracing::info!("Received extension notification: method={}", args.method);
|
||||
if args.method.as_ref() == "x.ai/yolo_mode_changed"
|
||||
if args.method.as_ref() == "kigi/yolo_mode_changed"
|
||||
&& let Ok(params) = serde_json::from_str::<
|
||||
serde_json::Value,
|
||||
>(args.params.get())
|
||||
@@ -2257,7 +2257,7 @@ impl acp::Agent for MvpAgent {
|
||||
);
|
||||
}
|
||||
}
|
||||
if args.method.as_ref() == "x.ai/permissions/reset" {
|
||||
if args.method.as_ref() == "kigi/permissions/reset" {
|
||||
let sessions = self.sessions.borrow();
|
||||
let updated = sessions
|
||||
.values()
|
||||
@@ -2273,10 +2273,10 @@ impl acp::Agent for MvpAgent {
|
||||
"Permission state reset for matching sessions"
|
||||
);
|
||||
}
|
||||
if args.method.as_ref() == "x.ai/internal/evict_sessions" {
|
||||
if args.method.as_ref() == "kigi/internal/evict_sessions" {
|
||||
self.handle_evict_sessions(&args.params).await;
|
||||
}
|
||||
if args.method.as_ref() == "x.ai/toggle_plan_mode"
|
||||
if args.method.as_ref() == "kigi/toggle_plan_mode"
|
||||
&& let Ok(params) = serde_json::from_str::<
|
||||
serde_json::Value,
|
||||
>(args.params.get())
|
||||
@@ -2317,8 +2317,8 @@ impl acp::Agent for MvpAgent {
|
||||
}
|
||||
}
|
||||
if matches!(
|
||||
args.method.as_ref(), "x.ai/queue/remove" | "x.ai/queue/reorder" |
|
||||
"x.ai/queue/clear" | "x.ai/queue/edit" | "x.ai/queue/interject"
|
||||
args.method.as_ref(), "kigi/queue/remove" | "kigi/queue/reorder" |
|
||||
"kigi/queue/clear" | "kigi/queue/edit" | "kigi/queue/interject"
|
||||
)
|
||||
&& let Ok(params) = serde_json::from_str::<
|
||||
serde_json::Value,
|
||||
@@ -2358,14 +2358,14 @@ impl acp::Agent for MvpAgent {
|
||||
);
|
||||
}
|
||||
}
|
||||
if args.method.as_ref() == "x.ai/terminal/pty/input"
|
||||
if args.method.as_ref() == "kigi/terminal/pty/input"
|
||||
&& let Ok(params) = serde_json::from_str::<
|
||||
serde_json::Value,
|
||||
>(args.params.get())
|
||||
{
|
||||
crate::extensions::terminal::handle_pty_input(¶ms).await;
|
||||
}
|
||||
if args.method.as_ref() == "_x.ai/session/update" {
|
||||
if args.method.as_ref() == "_kigi/session/update" {
|
||||
if let Ok(notification) = serde_json::from_str::<
|
||||
SessionNotification,
|
||||
>(args.params.get()) {
|
||||
@@ -2393,7 +2393,7 @@ impl acp::Agent for MvpAgent {
|
||||
tracing::warn!("Failed to parse xAI session notification params");
|
||||
}
|
||||
}
|
||||
if args.method.as_ref() == "x.ai/telemetry/non_git_decision" {
|
||||
if args.method.as_ref() == "kigi/telemetry/non_git_decision" {
|
||||
#[derive(serde::Deserialize)]
|
||||
struct NonGitDecisionParams {
|
||||
decision: String,
|
||||
@@ -2412,7 +2412,7 @@ impl acp::Agent for MvpAgent {
|
||||
tracing::warn!("Failed to parse non_git_decision telemetry params");
|
||||
}
|
||||
}
|
||||
if args.method.as_ref() == "x.ai/telemetry/multi_agent_followup" {
|
||||
if args.method.as_ref() == "kigi/telemetry/multi_agent_followup" {
|
||||
#[derive(serde::Deserialize)]
|
||||
struct MultiAgentFollowupParams {
|
||||
preferred_agent_label: char,
|
||||
@@ -2433,7 +2433,7 @@ impl acp::Agent for MvpAgent {
|
||||
tracing::warn!("Failed to parse multi-agent followup telemetry params");
|
||||
}
|
||||
}
|
||||
if args.method.as_ref() == "x.ai/telemetry/multi_agent_apply" {
|
||||
if args.method.as_ref() == "kigi/telemetry/multi_agent_apply" {
|
||||
#[derive(serde::Deserialize)]
|
||||
struct MultiAgentApplyParams {
|
||||
applied_agent_label: char,
|
||||
@@ -2454,7 +2454,7 @@ impl acp::Agent for MvpAgent {
|
||||
tracing::warn!("Failed to parse multi-agent apply telemetry params");
|
||||
}
|
||||
}
|
||||
if args.method.as_ref() == "x.ai/telemetry/multi_agent_discard" {
|
||||
if args.method.as_ref() == "kigi/telemetry/multi_agent_discard" {
|
||||
#[derive(serde::Deserialize)]
|
||||
struct MultiAgentDiscardParams {
|
||||
/// (label, session_id, model_id)
|
||||
|
||||
@@ -108,7 +108,7 @@ impl MvpAgent {
|
||||
}
|
||||
/// Resolve folder trust and load launch-dir MCP configs after `initialize`
|
||||
/// returns. The walks are synchronous and expensive in large monorepos; they
|
||||
/// must not block the ACP response (grok-desktop sends `initialize` immediately).
|
||||
/// must not block the ACP response (kigi-desktop sends `initialize` immediately).
|
||||
pub(super) fn spawn_initialize_launch_mcp_setup(&self) {
|
||||
let cwd = self.launch_cwd.clone();
|
||||
let compat = self.cfg.borrow().compat_resolved;
|
||||
@@ -151,7 +151,7 @@ impl MvpAgent {
|
||||
/// Build the launch-dir plugin registry snapshot on first use.
|
||||
///
|
||||
/// Boot-time discovery was deferred past ACP `initialize` (the cwd→git-root
|
||||
/// plus user/marketplace walks stalled grok-desktop's first `initialize`),
|
||||
/// plus user/marketplace walks stalled kigi-desktop's first `initialize`),
|
||||
/// leaving `plugin_registry_handle` empty. That shared snapshot still backs
|
||||
/// the launch-dir plugin MCP/LSP merges read in `resolve_mcp_servers` and
|
||||
/// the session LSP build, so populate it lazily — off the `initialize`
|
||||
@@ -292,7 +292,7 @@ impl MvpAgent {
|
||||
}
|
||||
/// Pre-session command availability snapshot.
|
||||
///
|
||||
/// Used by the `x.ai/commands/list` ext method and the
|
||||
/// Used by the `kigi/commands/list` ext method and the
|
||||
/// `InitializeResponse._meta` path (`builtin_commands()`), both of
|
||||
/// which fire before any session exists. The eventual agent's toolset
|
||||
/// is unknown (depends on the model the user picks), so we fail-closed
|
||||
@@ -346,7 +346,7 @@ impl MvpAgent {
|
||||
///
|
||||
/// Deferred past ACP wiring so `initialize` can respond before folder-trust
|
||||
/// scans and `WorkspaceHandle::new_minimal` run (same boot stall as plugin
|
||||
/// discovery on grok-desktop Windows).
|
||||
/// discovery on kigi-desktop Windows).
|
||||
fn ensure_local_workspace_ops(
|
||||
&self,
|
||||
) -> Result<kigi_workspace::WorkspaceOps, acp::Error> {
|
||||
@@ -404,7 +404,7 @@ impl MvpAgent {
|
||||
/// Returns `SessionToken` when EITHER:
|
||||
/// - `auth_manager` currently has a live (non-expired) credential, OR
|
||||
/// - the active auth method is session-based (`cached_token`,
|
||||
/// `grok.com`, `oidc`) -- even if the in-memory token is currently
|
||||
/// `kimi-code`, `oidc`) -- even if the in-memory token is currently
|
||||
/// expired or missing.
|
||||
///
|
||||
/// Returns `ApiKey` only when the auth method is BYOK (`xai.api_key`) or
|
||||
@@ -493,7 +493,7 @@ impl MvpAgent {
|
||||
let _ = self
|
||||
.gateway
|
||||
.ext_notification(
|
||||
acp::ExtNotification::new("x.ai/session_notification", params.into()),
|
||||
acp::ExtNotification::new("kigi/session_notification", params.into()),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -621,8 +621,8 @@ impl MvpAgent {
|
||||
/// Build deploy-service config. The tool talks directly to the deployer service.
|
||||
pub(super) fn prepare_app_builder_deployer_config(
|
||||
&self,
|
||||
) -> kigi_tools::implementations::grok_build::deploy_app::AppBuilderDeployerConfig {
|
||||
use kigi_tools::implementations::grok_build::deploy_app::AppBuilderDeployerConfig;
|
||||
) -> kigi_tools::implementations::kigi::deploy_app::AppBuilderDeployerConfig {
|
||||
use kigi_tools::implementations::kigi::deploy_app::AppBuilderDeployerConfig;
|
||||
AppBuilderDeployerConfig::Disabled
|
||||
}
|
||||
/// Web search config (PRD F5). The Kimi search service exists only on
|
||||
@@ -677,8 +677,8 @@ impl MvpAgent {
|
||||
/// OAuth sessions (PRD F5) > None (local pipeline only)
|
||||
pub(super) fn prepare_web_fetch_config(
|
||||
&self,
|
||||
) -> kigi_tools::implementations::grok_build::web_fetch::WebFetchConfig {
|
||||
use kigi_tools::implementations::grok_build::web_fetch::WebFetchConfig;
|
||||
) -> kigi_tools::implementations::kigi::web_fetch::WebFetchConfig {
|
||||
use kigi_tools::implementations::kigi::web_fetch::WebFetchConfig;
|
||||
let cfg = self.cfg.borrow();
|
||||
if cfg.disable_web_search {
|
||||
return WebFetchConfig::Disabled;
|
||||
@@ -816,7 +816,7 @@ impl MvpAgent {
|
||||
subagent_event_tx,
|
||||
subagent_event_rx: RefCell::new(Some(subagent_event_rx)),
|
||||
subagent_coordinator: RefCell::new(subagent_coordinator),
|
||||
monitor_event_buffer: kigi_tools::implementations::grok_build::task::types::MonitorEventBuffer::default(),
|
||||
monitor_event_buffer: kigi_tools::implementations::kigi::task::types::MonitorEventBuffer::default(),
|
||||
workspace_ops: RefCell::new(None),
|
||||
require_gateway_sessions: Rc::new(
|
||||
RefCell::new(std::collections::HashSet::new()),
|
||||
@@ -833,7 +833,7 @@ impl MvpAgent {
|
||||
instance.auth_manager.configure_refresher();
|
||||
instance
|
||||
}
|
||||
/// Handle `x.ai/internal/evict_sessions` — the leader server tells us a
|
||||
/// Handle `kigi/internal/evict_sessions` — the leader server tells us a
|
||||
/// client disconnected and these sessions lost their IPC owner.
|
||||
///
|
||||
/// **This is the no-evict keystone.** A disconnect must
|
||||
@@ -1082,12 +1082,12 @@ impl MvpAgent {
|
||||
}
|
||||
}
|
||||
/// Cancel a subagent by id, returning a typed outcome that backs the pager's
|
||||
/// `x.ai/subagent/cancel`. Active/pending → cancelled (a finish follows);
|
||||
/// `kigi/subagent/cancel`. Active/pending → cancelled (a finish follows);
|
||||
/// already-finished → its terminal status; unknown id → `NotFound`.
|
||||
pub fn cancel_subagent(
|
||||
&self,
|
||||
subagent_id: &str,
|
||||
) -> kigi_tools::implementations::grok_build::task::types::SubagentCancelOutcome {
|
||||
) -> kigi_tools::implementations::kigi::task::types::SubagentCancelOutcome {
|
||||
self.subagent_coordinator.borrow_mut().cancel_with_outcome(subagent_id)
|
||||
}
|
||||
/// List running subagent seeds for a given parent session.
|
||||
@@ -1183,7 +1183,7 @@ impl MvpAgent {
|
||||
let sessions = self.sessions.borrow();
|
||||
sessions.get(session_id).cloned()
|
||||
}
|
||||
/// Get hooks list for a session (for `x.ai/hooks/list` extension).
|
||||
/// Get hooks list for a session (for `kigi/hooks/list` extension).
|
||||
pub async fn list_hooks(
|
||||
&self,
|
||||
session_id: &acp::SessionId,
|
||||
@@ -1191,7 +1191,7 @@ impl MvpAgent {
|
||||
let handle = self.get_session_handle(session_id)?;
|
||||
handle.get_hooks_list().await
|
||||
}
|
||||
/// Execute a hooks management action (for `x.ai/hooks/action`).
|
||||
/// Execute a hooks management action (for `kigi/hooks/action`).
|
||||
pub async fn execute_hooks_action(
|
||||
&self,
|
||||
session_id: &acp::SessionId,
|
||||
@@ -1207,7 +1207,7 @@ impl MvpAgent {
|
||||
let handle = self.get_session_handle(session_id)?;
|
||||
handle.execute_hooks_action(action).await
|
||||
}
|
||||
/// Execute a plugins management action (for `x.ai/plugins/action`).
|
||||
/// Execute a plugins management action (for `kigi/plugins/action`).
|
||||
pub async fn execute_plugins_action(
|
||||
&self,
|
||||
session_id: &acp::SessionId,
|
||||
@@ -1225,7 +1225,7 @@ impl MvpAgent {
|
||||
}
|
||||
outcome
|
||||
}
|
||||
/// Get a snapshot of the shared plugin registry (for `x.ai/plugins/list`).
|
||||
/// Get a snapshot of the shared plugin registry (for `kigi/plugins/list`).
|
||||
pub fn plugin_registry_snapshot(
|
||||
&self,
|
||||
) -> Option<std::sync::Arc<kigi_agent::plugins::PluginRegistry>> {
|
||||
@@ -1352,7 +1352,7 @@ impl MvpAgent {
|
||||
current_effort,
|
||||
)
|
||||
}
|
||||
/// Build the `x.ai/sessionConfig` and `x.ai/sessionDetail` `_meta` values
|
||||
/// Build the `kigi/sessionConfig` and `kigi/sessionDetail` `_meta` values
|
||||
/// shared by `new_session` and `load_session`, returned as
|
||||
/// `(sessionConfig, sessionDetail)`. Keeping both response paths on this one
|
||||
/// builder stops them drifting.
|
||||
@@ -1364,7 +1364,7 @@ impl MvpAgent {
|
||||
model_state: &acp::SessionModelState,
|
||||
) -> (serde_json::Value, serde_json::Value) {
|
||||
let config_options = self.session_config_options(Some(session_id), model_state);
|
||||
let detail = session_config::GrokSessionDetail::build(
|
||||
let detail = session_config::KigiSessionDetail::build(
|
||||
session_id.0.to_string(),
|
||||
cwd,
|
||||
model_state.current_model_id.0.to_string(),
|
||||
@@ -1455,13 +1455,13 @@ impl MvpAgent {
|
||||
model_agent_type: Option<&str>,
|
||||
) -> kigi_agent::AgentDefinition {
|
||||
use kigi_agent::AgentDefinition;
|
||||
let grok_agent_env_set = std::env::var("KIGI_AGENT")
|
||||
let kigi_agent_env_set = std::env::var("KIGI_AGENT")
|
||||
.ok()
|
||||
.is_some_and(|s| !s.trim().is_empty());
|
||||
let config_agent_explicitly_set = agent_config.name.is_some();
|
||||
let model_requires_strict_harness = model_agent_type
|
||||
.is_some_and(kigi_agent::config::is_strict_harness_agent_type);
|
||||
if !grok_agent_env_set && !config_agent_explicitly_set
|
||||
if !kigi_agent_env_set && !config_agent_explicitly_set
|
||||
&& model_requires_strict_harness && let Some(required) = model_agent_type
|
||||
&& let Some(def) = kigi_agent::discovery::by_name_in_cwd(required, cwd)
|
||||
{
|
||||
@@ -1527,8 +1527,8 @@ impl MvpAgent {
|
||||
let agent_name = std::env::var("KIGI_AGENT").ok();
|
||||
let resolved = match agent_name.as_deref() {
|
||||
Some("browser-use") | Some("browser_use") => AgentDefinition::browser_use(),
|
||||
Some("grok-build-concise") | Some("grok_build_concise") => {
|
||||
AgentDefinition::grok_build_concise()
|
||||
Some("kigi-concise") | Some("kigi_concise") => {
|
||||
AgentDefinition::kigi_concise()
|
||||
}
|
||||
Some(path) if std::path::Path::new(path).is_absolute() => {
|
||||
match AgentDefinition::from_file(path) {
|
||||
@@ -1538,17 +1538,17 @@ impl MvpAgent {
|
||||
path = path, error = % e,
|
||||
"Failed to load agent definition from file, falling back to default"
|
||||
);
|
||||
AgentDefinition::grok_build_plan()
|
||||
AgentDefinition::kigi_plan()
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(name) => {
|
||||
kigi_agent::discovery::by_name_in_cwd(name, cwd)
|
||||
.unwrap_or_else(AgentDefinition::grok_build_plan)
|
||||
.unwrap_or_else(AgentDefinition::kigi_plan)
|
||||
}
|
||||
None => AgentDefinition::grok_build_plan(),
|
||||
None => AgentDefinition::kigi_plan(),
|
||||
};
|
||||
if !grok_agent_env_set && !config_agent_explicitly_set
|
||||
if !kigi_agent_env_set && !config_agent_explicitly_set
|
||||
&& model_requires_strict_harness && let Some(required) = model_agent_type
|
||||
&& resolved.name != required
|
||||
{
|
||||
@@ -1638,7 +1638,7 @@ impl MvpAgent {
|
||||
.client_capabilities
|
||||
.meta
|
||||
.as_ref()
|
||||
.and_then(|m| m.get("x.ai/fs_notify"))
|
||||
.and_then(|m| m.get("kigi/fs_notify"))
|
||||
.and_then(|v| {
|
||||
use crate::session::{ClientFsConfig, ClientFsMode};
|
||||
use kigi_fsnotify::FsConfig;
|
||||
@@ -1712,7 +1712,7 @@ impl MvpAgent {
|
||||
.client_capabilities
|
||||
.meta
|
||||
.as_ref()
|
||||
.and_then(|m| m.get("x.ai/hunkTracker"))
|
||||
.and_then(|m| m.get("kigi/hunkTracker"))
|
||||
.and_then(|v| v.get("mode"))
|
||||
.and_then(|v| v.as_str()),
|
||||
);
|
||||
@@ -1720,14 +1720,14 @@ impl MvpAgent {
|
||||
.client_capabilities
|
||||
.meta
|
||||
.as_ref()
|
||||
.and_then(|m| m.get("x.ai/incrementalBashOutput"))
|
||||
.and_then(|m| m.get("kigi/incrementalBashOutput"))
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
let no_color = init
|
||||
.client_capabilities
|
||||
.meta
|
||||
.as_ref()
|
||||
.and_then(|m| m.get("x.ai/bashOutputNoColor"))
|
||||
.and_then(|m| m.get("kigi/bashOutputNoColor"))
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
let hunk_tracking_enabled = hunk_plan.enabled();
|
||||
@@ -2200,7 +2200,7 @@ impl MvpAgent {
|
||||
.client_capabilities
|
||||
.meta
|
||||
.as_ref()
|
||||
.and_then(|m| m.get("x.ai/gitHeadChanged"))
|
||||
.and_then(|m| m.get("kigi/gitHeadChanged"))
|
||||
.and_then(|v| v.as_bool());
|
||||
let fs_watch_caps = crate::session::fs_watch::FsWatchCapabilities::resolve(crate::session::fs_watch::CapabilityInputs {
|
||||
client_notify: fs_notify_config.is_some(),
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
use super::*;
|
||||
|
||||
impl MvpAgent {
|
||||
/// Parse the `x.ai/codeNavigation.enabled` capability from an initialize
|
||||
/// Parse the `kigi/codeNavigation.enabled` capability from an initialize
|
||||
/// request. Returns `false` if the field is absent or not `true`.
|
||||
pub(crate) fn parse_code_nav_capability(init: &acp::InitializeRequest) -> bool {
|
||||
init.client_capabilities
|
||||
.meta
|
||||
.as_ref()
|
||||
.and_then(|m| m.get("x.ai/codeNavigation"))
|
||||
.and_then(|m| m.get("kigi/codeNavigation"))
|
||||
.and_then(|v| v.get("enabled"))
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false)
|
||||
@@ -56,7 +56,7 @@ impl MvpAgent {
|
||||
use crate::agent::config::CodebaseIndexingSetting;
|
||||
|
||||
// Gate 1: client type
|
||||
if !matches!(client_type, ClientType::GrokWeb) {
|
||||
if !matches!(client_type, ClientType::KigiWeb) {
|
||||
tracing::info!(
|
||||
client_type = ?client_type,
|
||||
gate = "client_type",
|
||||
@@ -71,7 +71,7 @@ impl MvpAgent {
|
||||
tracing::info!(
|
||||
gate = "capability",
|
||||
skip_reason = "capability_not_advertised",
|
||||
"code-nav eligibility check: skipping (x.ai/codeNavigation.enabled not advertised)"
|
||||
"code-nav eligibility check: skipping (kigi/codeNavigation.enabled not advertised)"
|
||||
);
|
||||
return Err(CodeNavEligibility::CapabilityNotAdvertised);
|
||||
}
|
||||
@@ -138,7 +138,7 @@ impl MvpAgent {
|
||||
Some(sid) => sid,
|
||||
// No session_id: per-client capability cannot be determined without a
|
||||
// session. Reject with SessionRequired rather than fall back to shared
|
||||
// global state. Callers must provide sessionId for x.ai/code/* requests.
|
||||
// global state. Callers must provide sessionId for kigi/code/* requests.
|
||||
None => return Err(CodeNavEligibility::SessionRequired),
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//! Interactive folder-trust prompt: a dormant agent→GUI-client ACP round-trip
|
||||
//! (`x.ai/folder_trust/request`) that asks a GUI client (grok-desktop) to decide
|
||||
//! (`kigi/folder_trust/request`) that asks a GUI client (kigi-desktop) to decide
|
||||
//! trust for an untrusted-with-configs workspace, then grants + reloads the
|
||||
//! now-trusted project servers without a restart.
|
||||
//!
|
||||
//! DORMANT in production: it only fires when the connected client advertised
|
||||
//! `x.ai/folderTrust.interactive` AND the folder-trust feature flag is on AND the
|
||||
//! `kigi/folderTrust.interactive` AND the folder-trust feature flag is on AND the
|
||||
//! verdict is [`kigi_workspace::folder_trust::TrustOutcome::Prompt`]. No
|
||||
//! client advertises the capability until the desktop UI ships — so this is
|
||||
//! inert by default even with the feature flag on. The TUI/headless clients never
|
||||
@@ -29,7 +29,7 @@ use super::*;
|
||||
/// the whole connection lifetime.
|
||||
const TRUST_PROMPT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30 * 60);
|
||||
|
||||
/// ACP `x.ai/folder_trust/request` payload (agent → GUI client). Serialized as
|
||||
/// ACP `kigi/folder_trust/request` payload (agent → GUI client). Serialized as
|
||||
/// `camelCase` for the ACP JSON-RPC wire format.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -62,21 +62,21 @@ pub(crate) enum FolderTrustOutcome {
|
||||
Reject,
|
||||
}
|
||||
|
||||
/// ACP `x.ai/folder_trust/request` response (GUI client → agent).
|
||||
/// ACP `kigi/folder_trust/request` response (GUI client → agent).
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
pub(crate) struct FolderTrustResponse {
|
||||
pub outcome: FolderTrustOutcome,
|
||||
}
|
||||
|
||||
impl MvpAgent {
|
||||
/// Parse the `x.ai/folderTrust.interactive` capability from an initialize
|
||||
/// Parse the `kigi/folderTrust.interactive` capability from an initialize
|
||||
/// request. Returns `false` if absent or not `true`. Mirrors
|
||||
/// [`Self::parse_code_nav_capability`].
|
||||
pub(crate) fn parse_interactive_trust_capability(init: &acp::InitializeRequest) -> bool {
|
||||
init.client_capabilities
|
||||
.meta
|
||||
.as_ref()
|
||||
.and_then(|m| m.get("x.ai/folderTrust"))
|
||||
.and_then(|m| m.get("kigi/folderTrust"))
|
||||
.and_then(|v| v.get("interactive"))
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false)
|
||||
@@ -84,7 +84,7 @@ impl MvpAgent {
|
||||
|
||||
/// Ask a GUI client to decide trust for `session_id`'s workspace, then grant
|
||||
/// + reload on accept. DORMANT no-op unless the client advertised
|
||||
/// `x.ai/folderTrust.interactive` AND [`folder_trust::prompt_warranted`]
|
||||
/// `kigi/folderTrust.interactive` AND [`folder_trust::prompt_warranted`]
|
||||
/// (feature on + untrusted + repo configs present).
|
||||
///
|
||||
/// Non-blocking: the session was already created with project servers GATED
|
||||
@@ -181,7 +181,7 @@ impl MvpAgent {
|
||||
return;
|
||||
}
|
||||
};
|
||||
let ext_request = acp::ExtRequest::new("x.ai/folder_trust/request", raw_params.into());
|
||||
let ext_request = acp::ExtRequest::new("kigi/folder_trust/request", raw_params.into());
|
||||
|
||||
use agent_client_protocol::Client as _;
|
||||
let outcome = match tokio::time::timeout(
|
||||
@@ -374,7 +374,7 @@ mod tests {
|
||||
fn parse_interactive_trust_capability_present_and_true() {
|
||||
let mut meta = serde_json::Map::new();
|
||||
meta.insert(
|
||||
"x.ai/folderTrust".to_string(),
|
||||
"kigi/folderTrust".to_string(),
|
||||
serde_json::json!({ "interactive": true }),
|
||||
);
|
||||
let init = init_with_meta(Some(serde_json::Value::Object(meta)));
|
||||
@@ -391,7 +391,7 @@ mod tests {
|
||||
fn parse_interactive_trust_capability_false_returns_false() {
|
||||
let mut meta = serde_json::Map::new();
|
||||
meta.insert(
|
||||
"x.ai/folderTrust".to_string(),
|
||||
"kigi/folderTrust".to_string(),
|
||||
serde_json::json!({ "interactive": false }),
|
||||
);
|
||||
let init = init_with_meta(Some(serde_json::Value::Object(meta)));
|
||||
|
||||
@@ -85,7 +85,7 @@ use tokio_util::sync::CancellationToken;
|
||||
use kigi_paths::AbsPathBuf;
|
||||
use kigi_workspace::session::git::GitDiscoveryResult;
|
||||
use kigi_hunk_tracker::HunkTrackerActor;
|
||||
/// Hard-error message for legacy Direct hub-bind sessions (`x.ai/cloud_server_id`).
|
||||
/// Hard-error message for legacy Direct hub-bind sessions (`kigi/cloud_server_id`).
|
||||
pub(crate) const DIRECT_HUB_CLOUD_REMOVED_MSG: &str = "Direct hub cloud removed; use Gateway (envId or existing-workspace attach)";
|
||||
/// Reject session `_meta` that still requests Direct hub bind (D8).
|
||||
///
|
||||
@@ -93,7 +93,7 @@ pub(crate) const DIRECT_HUB_CLOUD_REMOVED_MSG: &str = "Direct hub cloud removed;
|
||||
pub(crate) fn reject_direct_hub_cloud_meta(
|
||||
session_meta: Option<&acp::Meta>,
|
||||
) -> Result<(), acp::Error> {
|
||||
if session_meta.and_then(|m| m.get("x.ai/cloud_server_id")).is_some() {
|
||||
if session_meta.and_then(|m| m.get("kigi/cloud_server_id")).is_some() {
|
||||
return Err(acp::Error::invalid_params().data(DIRECT_HUB_CLOUD_REMOVED_MSG));
|
||||
}
|
||||
Ok(())
|
||||
@@ -147,13 +147,13 @@ impl BridgeAttach {
|
||||
!matches!(self, Self::NotAttached)
|
||||
}
|
||||
}
|
||||
/// `_meta["x.ai/session"].kind` → [`SessionKind`]; absent/unknown/malformed → `Build`.
|
||||
/// `_meta["kigi/session"].kind` → [`SessionKind`]; absent/unknown/malformed → `Build`.
|
||||
fn parse_session_kind(
|
||||
meta: Option<&acp::Meta>,
|
||||
) -> crate::session::unified_list::SessionKind {
|
||||
use crate::session::unified_list::SessionKind;
|
||||
use serde::Deserialize;
|
||||
meta.and_then(|m| m.get("x.ai/session"))
|
||||
meta.and_then(|m| m.get("kigi/session"))
|
||||
.and_then(|s| s.get("kind"))
|
||||
.and_then(|k| SessionKind::deserialize(k).ok())
|
||||
.unwrap_or(SessionKind::Build)
|
||||
@@ -191,7 +191,7 @@ fn chat_new_session_model_state(
|
||||
/// `session/new` / `session/load` `_meta` key carrying per-session plugin roots.
|
||||
pub(crate) const SESSION_PLUGIN_DIRS_META_KEY: &str = "pluginDirs";
|
||||
/// `initialize` response `_meta` key advertising [`SESSION_PLUGIN_DIRS_META_KEY`] support.
|
||||
pub(crate) const SESSION_PLUGIN_DIRS_CAPABILITY_KEY: &str = "x.ai/pluginDirs";
|
||||
pub(crate) const SESSION_PLUGIN_DIRS_CAPABILITY_KEY: &str = "kigi/pluginDirs";
|
||||
/// Per-session plugin roots from `session/new` / `session/load` `_meta.pluginDirs`,
|
||||
/// loaded at CliOverride scope (always trusted) into this session's registry only.
|
||||
/// Paths must be absolute (the SDKs resolve before sending); anything else is
|
||||
@@ -271,7 +271,7 @@ fn parse_no_replay(meta: Option<&acp::Meta>) -> bool {
|
||||
meta.and_then(|m| m.get("noReplay")).and_then(|v| v.as_bool()).unwrap_or(false)
|
||||
}
|
||||
/// Insert `key`/`value` into a notification's `_meta`, creating the map if absent.
|
||||
/// Used to stamp `x.ai/leaderClientId` onto replay notifications so the leader can
|
||||
/// Used to stamp `kigi/leaderClientId` onto replay notifications so the leader can
|
||||
/// unicast them to the loading client only (see `forward_raw_replay_line`).
|
||||
fn stamp_meta_value(meta: &mut Option<acp::Meta>, key: &str, value: &serde_json::Value) {
|
||||
meta.get_or_insert_with(acp::Meta::new).insert(key.to_string(), value.clone());
|
||||
@@ -284,7 +284,7 @@ fn mark_as_replay(
|
||||
let obj = meta.get_or_insert_with(acp::Meta::new);
|
||||
obj.insert("isReplay".to_string(), is_replay);
|
||||
if let Some(persist) = persist_data {
|
||||
obj.insert("x.ai/persist".to_string(), persist.clone());
|
||||
obj.insert("kigi/persist".to_string(), persist.clone());
|
||||
}
|
||||
}
|
||||
/// Resolve a session's REQUESTED auto flag from `_meta`: an explicit `autoMode`
|
||||
@@ -393,7 +393,7 @@ pub(crate) fn build_prompt_response_meta(
|
||||
};
|
||||
serde_json::to_value(meta).expect("PromptResponseMeta is always serializable")
|
||||
}
|
||||
/// Typed payload for the `x.ai/settings/update` notification sent to pager
|
||||
/// Typed payload for the `kigi/settings/update` notification sent to pager
|
||||
/// clients after remote settings settings are refreshed on `/new`.
|
||||
///
|
||||
/// Keeping this as a `#[derive(Serialize)]` struct gives compile-time
|
||||
@@ -413,13 +413,13 @@ struct SettingsUpdateNotification {
|
||||
/// Reason why a client is not eligible to use codebase indexing.
|
||||
///
|
||||
/// Returned by [`MvpAgent::code_nav_eligibility`] when one of the policy
|
||||
/// gates fails. Used in `x.ai/code/status` responses and to generate
|
||||
/// gates fails. Used in `kigi/code/status` responses and to generate
|
||||
/// clear error messages on code-nav requests from ineligible clients.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CodeNavEligibility {
|
||||
/// Client type is not web (web-only for initial rollout).
|
||||
ClientNotWeb,
|
||||
/// Client did not advertise `x.ai/codeNavigation.enabled`.
|
||||
/// Client did not advertise `kigi/codeNavigation.enabled`.
|
||||
CapabilityNotAdvertised,
|
||||
/// `codebase_indexing` feature is disabled in config (or excluded by glob).
|
||||
DisabledByConfig,
|
||||
@@ -519,12 +519,12 @@ pub struct MvpAgent {
|
||||
/// attribution would require threading `clientIdentifier` from `_meta` through
|
||||
/// every session handler, which is deferred to future work.
|
||||
client_type: RefCell<ClientType>,
|
||||
/// Whether the current client advertised `x.ai/codeNavigation.enabled`.
|
||||
/// Whether the current client advertised `kigi/codeNavigation.enabled`.
|
||||
/// Updated on every `initialize()` call — same last-client-wins semantics
|
||||
/// as `client_type`. Using `Cell<bool>` (not `RefCell`) so `.get()` is a
|
||||
/// plain copy with no borrow that could be held across an await point.
|
||||
code_nav_enabled: std::cell::Cell<bool>,
|
||||
/// Whether the current client advertised `x.ai/folderTrust.interactive` (it
|
||||
/// Whether the current client advertised `kigi/folderTrust.interactive` (it
|
||||
/// can render the interactive folder-trust prompt). Set on every
|
||||
/// `initialize()` (last-client-wins, like `code_nav_enabled`); gates the
|
||||
/// DORMANT agent→client trust round-trip in `new_session`/`load_session`.
|
||||
@@ -606,14 +606,14 @@ pub struct MvpAgent {
|
||||
/// Unified sender for all subagent coordinator events.
|
||||
/// LEADER-SAFE(shared): channel is multi-producer, coordinator drains.
|
||||
subagent_event_tx: tokio::sync::mpsc::UnboundedSender<
|
||||
kigi_tools::implementations::grok_build::task::types::SubagentEvent,
|
||||
kigi_tools::implementations::kigi::task::types::SubagentEvent,
|
||||
>,
|
||||
/// Receiver for subagent events. Taken once by `start_subagent_coordinator()`.
|
||||
/// `None` after the coordinator drain task has been spawned.
|
||||
subagent_event_rx: RefCell<
|
||||
Option<
|
||||
tokio::sync::mpsc::UnboundedReceiver<
|
||||
kigi_tools::implementations::grok_build::task::types::SubagentEvent,
|
||||
kigi_tools::implementations::kigi::task::types::SubagentEvent,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
@@ -624,7 +624,7 @@ pub struct MvpAgent {
|
||||
/// Pushed by the `InjectNotification` handler when a turn is active and the
|
||||
/// notification has `Next` priority. Drained by the session turn loop
|
||||
/// (`inject_pending_monitor_events`) into a hidden synthetic user message.
|
||||
monitor_event_buffer: kigi_tools::implementations::grok_build::task::types::MonitorEventBuffer,
|
||||
monitor_event_buffer: kigi_tools::implementations::kigi::task::types::MonitorEventBuffer,
|
||||
/// Per-subagent model ID overrides from config.toml `[subagents.models]`.
|
||||
/// Populated from `SubagentsConfig.models` during `with_models()`.
|
||||
subagent_model_overrides: std::collections::HashMap<String, String>,
|
||||
@@ -653,7 +653,7 @@ pub struct MvpAgent {
|
||||
/// `plugin_registry_handle`.
|
||||
///
|
||||
/// Boot-time plugin discovery is deferred past ACP `initialize` (it walks
|
||||
/// cwd→git root plus user/marketplace dirs and stalled grok-desktop's first
|
||||
/// cwd→git root plus user/marketplace dirs and stalled kigi-desktop's first
|
||||
/// `initialize`), so the shared snapshot starts empty. It is built once on
|
||||
/// the first session-creating call via [`Self::ensure_plugin_registry`];
|
||||
/// this flag keeps that to a single discovery walk.
|
||||
@@ -753,8 +753,8 @@ pub(crate) fn inherited_harness_template(
|
||||
///
|
||||
/// When a zero-turn switch rebuilds the harness (`did_rebuild`), the handle
|
||||
/// must adopt the rebuilt harness's agent type. Otherwise the name is left
|
||||
/// unchanged — compatible stock switches (e.g. `grok-build` →
|
||||
/// `grok-build-plan`) intentionally preserve the session's original ACP
|
||||
/// unchanged — compatible stock switches (e.g. `kigi` →
|
||||
/// `kigi-plan`) intentionally preserve the session's original ACP
|
||||
/// `agentProfile`.
|
||||
pub(crate) fn agent_name_after_model_switch(
|
||||
did_rebuild: bool,
|
||||
@@ -770,8 +770,8 @@ pub(crate) fn agent_name_after_model_switch(
|
||||
/// Harness compatibility for zero-turn / mid-turn model switching.
|
||||
///
|
||||
/// Two stock (non-strict) agents are interchangeable — they share the
|
||||
/// default wire format and toolset, so switching e.g. `grok-build` →
|
||||
/// `grok-build-plan` doesn't require rebuilding the harness and would
|
||||
/// default wire format and toolset, so switching e.g. `kigi` →
|
||||
/// `kigi-plan` doesn't require rebuilding the harness and would
|
||||
/// destroy a client-supplied `_meta.agentProfile` if it did.
|
||||
///
|
||||
/// Strict harnesses (`codex`, …) are only compatible with
|
||||
@@ -927,7 +927,7 @@ fn resolve_inference_idle_timeout_secs(
|
||||
let remote = remote_settings.and_then(|s| s.inference_idle_timeout_secs);
|
||||
per_model.or(remote).unwrap_or(600).max(10)
|
||||
}
|
||||
/// Parse the client-advertised `x.ai/hunkTracker.mode` string. Case-insensitive
|
||||
/// Parse the client-advertised `kigi/hunkTracker.mode` string. Case-insensitive
|
||||
/// and trimmed. Absent/blank/`off`/`disabled` => `None`; unknown => `AllDirty`.
|
||||
fn resolve_hunk_tracking_mode(
|
||||
mode_str: Option<&str>,
|
||||
@@ -1025,7 +1025,7 @@ impl MvpAgent {
|
||||
/// Dispatches by on-disk method name:
|
||||
/// - ACP updates (`"session/update"`) → typed `SessionNotification` for correct
|
||||
/// TUI dispatch (direct dispatch preserves Rust types, not method strings).
|
||||
/// - xAI updates (`"_x.ai/session/update"`) → `ExtNotification`.
|
||||
/// - xAI updates (`"_kigi/session/update"`) → `ExtNotification`.
|
||||
///
|
||||
/// When `mark_replay` is true, the notification is tagged with
|
||||
/// `_meta.isReplay: true` so the client knows it's historical data.
|
||||
@@ -1058,7 +1058,7 @@ impl MvpAgent {
|
||||
tracing::debug!("replay: skipping JSONL line with no params");
|
||||
return;
|
||||
};
|
||||
let is_xai = method == "_x.ai/session/update";
|
||||
let is_xai = method == "_kigi/session/update";
|
||||
if is_xai {
|
||||
if target_client_id.is_none() && !mark_replay {
|
||||
if let Ok(owned) = serde_json::value::RawValue::from_string(
|
||||
@@ -1070,7 +1070,7 @@ impl MvpAgent {
|
||||
.gateway
|
||||
.forward_with_completion(
|
||||
acp::ExtNotification::new(
|
||||
"x.ai/session/update",
|
||||
"kigi/session/update",
|
||||
std::sync::Arc::from(owned),
|
||||
),
|
||||
),
|
||||
@@ -1094,10 +1094,10 @@ impl MvpAgent {
|
||||
m.insert("isReplay".to_string(), serde_json::json!(true));
|
||||
}
|
||||
if let Some(pd) = persist_data {
|
||||
m.insert("x.ai/persist".to_string(), pd.clone());
|
||||
m.insert("kigi/persist".to_string(), pd.clone());
|
||||
}
|
||||
if let Some(tid) = target_client_id {
|
||||
m.insert("x.ai/leaderClientId".to_string(), tid.clone());
|
||||
m.insert("kigi/leaderClientId".to_string(), tid.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1108,7 +1108,7 @@ impl MvpAgent {
|
||||
.gateway
|
||||
.forward_with_completion(
|
||||
acp::ExtNotification::new(
|
||||
"x.ai/session/update",
|
||||
"kigi/session/update",
|
||||
std::sync::Arc::from(raw_val),
|
||||
),
|
||||
),
|
||||
@@ -1161,7 +1161,7 @@ impl MvpAgent {
|
||||
mark_as_replay(&mut notification.meta, persist_data);
|
||||
}
|
||||
if let Some(tid) = target_client_id {
|
||||
stamp_meta_value(&mut notification.meta, "x.ai/leaderClientId", tid);
|
||||
stamp_meta_value(&mut notification.meta, "kigi/leaderClientId", tid);
|
||||
}
|
||||
completions.push(self.gateway.forward_with_completion(notification));
|
||||
}
|
||||
@@ -1425,7 +1425,7 @@ impl MvpAgent {
|
||||
.gateway
|
||||
.forward_with_completion(
|
||||
acp::ExtNotification::new(
|
||||
"x.ai/task_completed",
|
||||
"kigi/task_completed",
|
||||
params.into(),
|
||||
),
|
||||
),
|
||||
@@ -1507,7 +1507,7 @@ impl MvpAgent {
|
||||
});
|
||||
AuthenticateResponse::new().meta(meta)
|
||||
}
|
||||
/// Fire-and-forget `x.ai/settings/update` from the current remote snapshot.
|
||||
/// Fire-and-forget `kigi/settings/update` from the current remote snapshot.
|
||||
pub(super) fn emit_settings_update_notification(&self) {
|
||||
let payload = {
|
||||
let cfg = self.cfg.borrow();
|
||||
@@ -1528,7 +1528,7 @@ impl MvpAgent {
|
||||
if let Ok(params) = serde_json::value::to_raw_value(&payload) {
|
||||
self.gateway
|
||||
.forward_fire_and_forget(
|
||||
acp::ExtNotification::new("x.ai/settings/update", params.into()),
|
||||
acp::ExtNotification::new("kigi/settings/update", params.into()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@ fn args<'a>(
|
||||
|
||||
#[test]
|
||||
fn includes_baseline_keys_without_usage() {
|
||||
let meta = build_prompt_response_meta(args("sess-1", "prompt-1", 42_000, "grok-4.5"));
|
||||
let meta = build_prompt_response_meta(args("sess-1", "prompt-1", 42_000, "kigi-4.5"));
|
||||
assert_eq!(meta["sessionId"], "sess-1");
|
||||
assert_eq!(meta["requestId"], "prompt-1");
|
||||
assert_eq!(meta["promptId"], "prompt-1");
|
||||
assert_eq!(meta["totalTokens"], 42_000);
|
||||
assert_eq!(meta["modelId"], "grok-4.5");
|
||||
assert_eq!(meta["modelId"], "kigi-4.5");
|
||||
// No per-turn keys when usage is absent.
|
||||
assert!(meta.get("inputTokens").is_none());
|
||||
assert!(meta.get("outputTokens").is_none());
|
||||
@@ -46,7 +46,7 @@ fn enriches_meta_with_camelcase_token_keys() {
|
||||
};
|
||||
let meta = build_prompt_response_meta(PromptResponseMetaArgs {
|
||||
last_turn_usage: Some(&usage),
|
||||
..args("sess-1", "prompt-1", 1_700, "grok-4.5")
|
||||
..args("sess-1", "prompt-1", 1_700, "kigi-4.5")
|
||||
});
|
||||
// Bot's _META_TOKEN_KEY_MAP expects exactly these camelCase keys.
|
||||
assert_eq!(meta["inputTokens"], 1500);
|
||||
|
||||
@@ -12,7 +12,7 @@ impl MvpAgent {
|
||||
/// Finalize the cloud session replica (fire-and-forget, "Hook 4").
|
||||
///
|
||||
/// Marks the session **done** upstream, so this MUST only run on a genuine
|
||||
/// session end — a terminal/explicit close (`x.ai/session/close`). It must
|
||||
/// session end — a terminal/explicit close (`kigi/session/close`). It must
|
||||
/// NOT run on a mere client disconnect or a dead-actor reap: those leave the
|
||||
/// conversation resumable on disk, and finalizing would wrongly mark a still
|
||||
/// running/resumable session "done".
|
||||
@@ -58,7 +58,7 @@ impl MvpAgent {
|
||||
.clone()
|
||||
}
|
||||
/// Close a session in response to an **explicit** terminal close
|
||||
/// (`x.ai/session/close`). Finalizes the cloud replica (genuine session
|
||||
/// (`kigi/session/close`). Finalizes the cloud replica (genuine session
|
||||
/// end), then removes the session terminally as `Completed`.
|
||||
pub(crate) fn close_session_explicit(&self, id: &acp::SessionId) {
|
||||
self.finalize_session_replica(id);
|
||||
@@ -76,7 +76,7 @@ impl MvpAgent {
|
||||
self.session_live_state.borrow().get(id).copied()
|
||||
}
|
||||
/// Roster-delta hook for a terminally removed session. Broadcasts an
|
||||
/// `x.ai/sessions/changed` notification with the session in `removed` so
|
||||
/// `kigi/sessions/changed` notification with the session in `removed` so
|
||||
/// every attached dashboard drops the row promptly. Also
|
||||
/// records the call site (and the terminal state) for test observability,
|
||||
/// since the `session_live_state` entry is dropped on removal.
|
||||
@@ -91,14 +91,14 @@ impl MvpAgent {
|
||||
self.emit_roster_changed(Vec::new(), vec![id.0.to_string()]);
|
||||
}
|
||||
/// Roster-delta hook for a newly-resident / changed session. Broadcasts an
|
||||
/// `x.ai/sessions/changed` notification with the current entry in
|
||||
/// `kigi/sessions/changed` notification with the current entry in
|
||||
/// `upserted` so dashboards add/refresh the row.
|
||||
pub(crate) fn push_roster_delta_upserted(&self, id: &acp::SessionId) {
|
||||
if let Some(entry) = self.resident_roster_entry(id) {
|
||||
self.emit_roster_changed(vec![entry], Vec::new());
|
||||
}
|
||||
}
|
||||
/// Emit an `x.ai/sessions/changed` upsert for a resident session with an
|
||||
/// Emit an `kigi/sessions/changed` upsert for a resident session with an
|
||||
/// explicit `activity`, so every attached dashboard reflects a
|
||||
/// turn-boundary transition (Working / Idle / NeedsInput) *immediately*
|
||||
/// rather than waiting for the ≤1s roster poll (deltas are emitted
|
||||
@@ -124,11 +124,11 @@ impl MvpAgent {
|
||||
self.emit_roster_changed(vec![entry], Vec::new());
|
||||
}
|
||||
}
|
||||
/// Fan an `x.ai/sessions/changed` delta out to every attached client.
|
||||
/// Fan an `kigi/sessions/changed` delta out to every attached client.
|
||||
///
|
||||
/// This is a roster-wide notification (no `sessionId`), so the leader IPC
|
||||
/// server broadcasts it to all clients rather than routing by session (see
|
||||
/// the `x.ai/sessions/changed` special-case in `leader/server.rs`).
|
||||
/// the `kigi/sessions/changed` special-case in `leader/server.rs`).
|
||||
pub(super) fn emit_roster_changed(
|
||||
&self,
|
||||
upserted: Vec<crate::agent::roster::RosterEntry>,
|
||||
|
||||
@@ -16,7 +16,7 @@ impl MvpAgent {
|
||||
};
|
||||
let agent_ref = LocalRef::new(self);
|
||||
use crate::agent::subagent::{BlockWaitSlot, is_running, resolve_snapshot};
|
||||
use kigi_tools::implementations::grok_build::task::types::{
|
||||
use kigi_tools::implementations::kigi::task::types::{
|
||||
SubagentCancelOutcome, SubagentCancelTarget, SubagentEvent,
|
||||
};
|
||||
tokio::task::spawn_local({
|
||||
@@ -208,7 +208,7 @@ impl MvpAgent {
|
||||
SubagentEvent::DescribeType(request) => {
|
||||
let agent_ref = agent_ref.clone();
|
||||
tokio::task::spawn_local(async move {
|
||||
use kigi_tools::implementations::grok_build::task::types::SubagentDescribeOutcome;
|
||||
use kigi_tools::implementations::kigi::task::types::SubagentDescribeOutcome;
|
||||
let this = agent_ref.get();
|
||||
let outcome = match this
|
||||
.try_build_subagent_spawn_context(&request.parent_session_id)
|
||||
|
||||
@@ -224,7 +224,7 @@ fn allocate_turn_number_advances_counter() {
|
||||
/// With no overrides and model_agent_type = None, the default agent is used.
|
||||
#[test]
|
||||
#[serial_test::serial]
|
||||
fn resolve_agent_definition_defaults_to_grok_build() {
|
||||
fn resolve_agent_definition_defaults_to_kigi() {
|
||||
let prev = std::env::var("KIGI_AGENT").ok();
|
||||
unsafe {
|
||||
std::env::remove_var("KIGI_AGENT");
|
||||
@@ -243,7 +243,7 @@ fn resolve_agent_definition_defaults_to_grok_build() {
|
||||
}
|
||||
}
|
||||
/// When model_agent_type = Some("codex"), the codex agent is selected even
|
||||
/// though the default chain would return grok-build.
|
||||
/// though the default chain would return kigi.
|
||||
#[test]
|
||||
#[serial_test::serial]
|
||||
fn resolve_agent_definition_model_agent_type_overrides_default() {
|
||||
@@ -320,13 +320,13 @@ fn resolve_agent_definition_acp_profile_wins_when_model_agent_type_is_default()
|
||||
}
|
||||
}
|
||||
/// Regression: after `DEFAULT_AGENT_TYPE` flipped to
|
||||
/// `grok-build-plan`, models in the catalog that still declare
|
||||
/// `agent_type = "grok-build"` explicitly must NOT preempt an ACP
|
||||
/// profile. Any value in the `grok-build*` family is the stock harness
|
||||
/// `kigi-plan`, models in the catalog that still declare
|
||||
/// `agent_type = "kigi"` explicitly must NOT preempt an ACP
|
||||
/// profile. Any value in the `kigi*` family is the stock harness
|
||||
/// with no strict requirement.
|
||||
#[test]
|
||||
#[serial_test::serial]
|
||||
fn resolve_agent_definition_acp_profile_wins_for_explicit_grok_build_family() {
|
||||
fn resolve_agent_definition_acp_profile_wins_for_explicit_kigi_family() {
|
||||
let prev = std::env::var("KIGI_AGENT").ok();
|
||||
unsafe {
|
||||
std::env::remove_var("KIGI_AGENT");
|
||||
@@ -337,7 +337,7 @@ fn resolve_agent_definition_acp_profile_wins_for_explicit_grok_build_family() {
|
||||
"Custom devbox profile", }
|
||||
))
|
||||
.expect("agent definition must parse");
|
||||
for family_variant in ["grok-build", "grok-build-plan", "grok-build-concise"] {
|
||||
for family_variant in ["kigi", "kigi-plan", "kigi-concise"] {
|
||||
let def = MvpAgent::resolve_agent_definition(
|
||||
tmp.path(),
|
||||
None,
|
||||
@@ -347,7 +347,7 @@ fn resolve_agent_definition_acp_profile_wins_for_explicit_grok_build_family() {
|
||||
);
|
||||
assert_eq!(
|
||||
def.name, "custom-devbox-profile",
|
||||
"ACP profile must win for grok-build family variant `{family_variant}`"
|
||||
"ACP profile must win for kigi family variant `{family_variant}`"
|
||||
);
|
||||
}
|
||||
if let Some(v) = prev {
|
||||
@@ -543,62 +543,62 @@ fn enqueue_replace_system_prompt_override_noop_when_absent_or_empty() {
|
||||
);
|
||||
}
|
||||
/// Regression for the web-client `_meta.agentProfile` -> `set_session_model`
|
||||
/// flow: a zero-turn switch from `grok-build` (a client profile name) to
|
||||
/// `grok-build-plan` (the default model agent_type) must be
|
||||
/// flow: a zero-turn switch from `kigi` (a client profile name) to
|
||||
/// `kigi-plan` (the default model agent_type) must be
|
||||
/// treated as compatible so the harness rebuild is skipped and the
|
||||
/// custom prompt body is preserved.
|
||||
#[test]
|
||||
fn harnesses_are_compatible_for_stock_family_pairs() {
|
||||
assert!(harnesses_are_compatible("grok-build", "grok-build-plan"));
|
||||
assert!(harnesses_are_compatible("grok-build-plan", "grok-build"));
|
||||
assert!(harnesses_are_compatible("grok-build", "grok-build"));
|
||||
assert!(harnesses_are_compatible("kigi", "kigi-plan"));
|
||||
assert!(harnesses_are_compatible("kigi-plan", "kigi"));
|
||||
assert!(harnesses_are_compatible("kigi", "kigi"));
|
||||
assert!(harnesses_are_compatible(
|
||||
"grok-build-concise",
|
||||
"grok-build-plan"
|
||||
"kigi-concise",
|
||||
"kigi-plan"
|
||||
));
|
||||
assert!(harnesses_are_compatible(
|
||||
"remote-sidebar",
|
||||
"grok-build-plan"
|
||||
"kigi-plan"
|
||||
));
|
||||
}
|
||||
#[test]
|
||||
fn harnesses_are_compatible_rejects_strict_mismatches() {
|
||||
assert!(harnesses_are_compatible("codex", "codex"));
|
||||
assert!(!harnesses_are_compatible("grok-build-plan", "codex"));
|
||||
assert!(!harnesses_are_compatible("kigi-plan", "codex"));
|
||||
}
|
||||
#[test]
|
||||
fn explicit_agent_type_wins_over_session_default() {
|
||||
assert_eq!(
|
||||
resolve_required_agent_type(Some("cursor"), "grok-build-plan"),
|
||||
resolve_required_agent_type(Some("cursor"), "kigi-plan"),
|
||||
"cursor"
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn null_agent_type_falls_back_to_session_default_grok_build_plan() {
|
||||
fn null_agent_type_falls_back_to_session_default_kigi_plan() {
|
||||
assert_eq!(
|
||||
resolve_required_agent_type(None, "grok-build-plan"),
|
||||
"grok-build-plan"
|
||||
resolve_required_agent_type(None, "kigi-plan"),
|
||||
"kigi-plan"
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn null_agent_type_falls_back_to_session_default_grok_build() {
|
||||
fn null_agent_type_falls_back_to_session_default_kigi() {
|
||||
assert_eq!(
|
||||
resolve_required_agent_type(None, "grok-build"),
|
||||
"grok-build"
|
||||
resolve_required_agent_type(None, "kigi"),
|
||||
"kigi"
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn null_agent_type_returns_to_session_default_after_cursor_switch() {
|
||||
let session_default = "grok-build-plan";
|
||||
let session_default = "kigi-plan";
|
||||
let required_after_null = resolve_required_agent_type(None, session_default);
|
||||
assert_eq!(required_after_null, "grok-build-plan");
|
||||
assert_eq!(required_after_null, "kigi-plan");
|
||||
assert_ne!(required_after_null, "cursor");
|
||||
}
|
||||
/// Compatible stock switches (no rebuild) must NOT mutate `agent_name`,
|
||||
/// preserving the session's original ACP `agentProfile`.
|
||||
#[test]
|
||||
fn agent_name_unchanged_without_harness_rebuild() {
|
||||
let unchanged = agent_name_after_model_switch(false, "grok-build-plan", "remote-sidebar");
|
||||
let unchanged = agent_name_after_model_switch(false, "kigi-plan", "remote-sidebar");
|
||||
assert_eq!(
|
||||
unchanged, "remote-sidebar",
|
||||
"a compatible stock switch must preserve the original agent profile name"
|
||||
@@ -648,7 +648,7 @@ async fn file_toolset_override_e2e_to_finalized_toolset() {
|
||||
web_search_config: kigi_tools::implementations::web_search::WebSearchConfig::default(),
|
||||
web_fetch_config: Default::default(),
|
||||
lsp: None,
|
||||
app_builder_deployer_config: kigi_tools::implementations::grok_build::deploy_app::AppBuilderDeployerConfig::default(),
|
||||
app_builder_deployer_config: kigi_tools::implementations::kigi::deploy_app::AppBuilderDeployerConfig::default(),
|
||||
api_key_provider: None,
|
||||
attribution_callback: None,
|
||||
system_reminder_tag: kigi_tools::reminders::DEFAULT_REMINDER_TAG,
|
||||
@@ -741,7 +741,7 @@ fn make_test_handle(
|
||||
force_compact: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
|
||||
permission_handle: kigi_workspace::permission::PermissionHandle::allow_all(),
|
||||
attribution_callback: None,
|
||||
agent_name: "grok-build".to_string(),
|
||||
agent_name: "kigi".to_string(),
|
||||
session_default_agent_profile: None,
|
||||
allowed_subagent_types: None,
|
||||
hook_registry: None,
|
||||
@@ -758,7 +758,7 @@ async fn lookup_session_model_returns_per_session_model() {
|
||||
let sid_b = acp::SessionId::new("sess-b");
|
||||
let default_model = acp::ModelId::new("default-model");
|
||||
let sessions: HashMap<acp::SessionId, crate::session::SessionHandle> = [
|
||||
(sid_a.clone(), make_test_handle("grok-3-fast", false, None)),
|
||||
(sid_a.clone(), make_test_handle("kigi-3-fast", false, None)),
|
||||
(sid_b.clone(), make_test_handle("codex-mini", false, None)),
|
||||
]
|
||||
.into();
|
||||
@@ -766,7 +766,7 @@ async fn lookup_session_model_returns_per_session_model() {
|
||||
lookup_session_model(&sessions, Some(&sid_a), &default_model)
|
||||
.0
|
||||
.as_ref(),
|
||||
"grok-3-fast"
|
||||
"kigi-3-fast"
|
||||
);
|
||||
assert_eq!(
|
||||
lookup_session_model(&sessions, Some(&sid_b), &default_model)
|
||||
@@ -778,13 +778,13 @@ async fn lookup_session_model_returns_per_session_model() {
|
||||
/// lookup_session_model falls back to the default when session_id is None.
|
||||
#[tokio::test]
|
||||
async fn lookup_session_model_fallback_no_session() {
|
||||
let default_model = acp::ModelId::new("grok-3");
|
||||
let default_model = acp::ModelId::new("kigi-3");
|
||||
let sessions: HashMap<acp::SessionId, crate::session::SessionHandle> = HashMap::new();
|
||||
assert_eq!(
|
||||
lookup_session_model(&sessions, None, &default_model)
|
||||
.0
|
||||
.as_ref(),
|
||||
"grok-3"
|
||||
"kigi-3"
|
||||
);
|
||||
}
|
||||
/// Mutating session A's model_id via the handle does not affect session B.
|
||||
@@ -794,8 +794,8 @@ async fn set_session_model_does_not_cross_contaminate() {
|
||||
let sid_b = acp::SessionId::new("sess-b");
|
||||
let default_model = acp::ModelId::new("default");
|
||||
let mut sessions: HashMap<acp::SessionId, crate::session::SessionHandle> = [
|
||||
(sid_a.clone(), make_test_handle("grok-3", false, None)),
|
||||
(sid_b.clone(), make_test_handle("grok-3", false, None)),
|
||||
(sid_a.clone(), make_test_handle("kigi-3", false, None)),
|
||||
(sid_b.clone(), make_test_handle("kigi-3", false, None)),
|
||||
]
|
||||
.into();
|
||||
sessions.get_mut(&sid_a).unwrap().model_id = acp::ModelId::new("codex-mini");
|
||||
@@ -809,7 +809,7 @@ async fn set_session_model_does_not_cross_contaminate() {
|
||||
lookup_session_model(&sessions, Some(&sid_b), &default_model)
|
||||
.0
|
||||
.as_ref(),
|
||||
"grok-3",
|
||||
"kigi-3",
|
||||
"Session B's model must not be affected by session A's model change"
|
||||
);
|
||||
}
|
||||
@@ -903,15 +903,15 @@ async fn yolo_toggle_scoped_by_client_identifier() {
|
||||
let mut sessions: HashMap<acp::SessionId, crate::session::SessionHandle> = [
|
||||
(
|
||||
sid_tui.clone(),
|
||||
make_test_handle("grok-3", false, Some("grok-tui")),
|
||||
make_test_handle("kigi-3", false, Some("kigi-tui")),
|
||||
),
|
||||
(
|
||||
sid_vscode.clone(),
|
||||
make_test_handle("grok-3", false, Some("grok-code-extension")),
|
||||
make_test_handle("kigi-3", false, Some("kigi-code-extension")),
|
||||
),
|
||||
]
|
||||
.into();
|
||||
let updated = apply_yolo_mode_to_matching_sessions(&mut sessions, Some("grok-tui"), true);
|
||||
let updated = apply_yolo_mode_to_matching_sessions(&mut sessions, Some("kigi-tui"), true);
|
||||
assert_eq!(updated, 1, "exactly one matching session should be updated");
|
||||
assert!(
|
||||
sessions[&sid_tui].yolo_mode,
|
||||
@@ -931,15 +931,15 @@ async fn yolo_toggle_can_disable_session_started_with_yolo_enabled() {
|
||||
let mut sessions: HashMap<acp::SessionId, crate::session::SessionHandle> = [
|
||||
(
|
||||
sid_tui.clone(),
|
||||
make_test_handle("grok-3", true, Some("grok-tui")),
|
||||
make_test_handle("kigi-3", true, Some("kigi-tui")),
|
||||
),
|
||||
(
|
||||
sid_other.clone(),
|
||||
make_test_handle("grok-3", true, Some("grok-code-extension")),
|
||||
make_test_handle("kigi-3", true, Some("kigi-code-extension")),
|
||||
),
|
||||
]
|
||||
.into();
|
||||
let updated = apply_yolo_mode_to_matching_sessions(&mut sessions, Some("grok-tui"), false);
|
||||
let updated = apply_yolo_mode_to_matching_sessions(&mut sessions, Some("kigi-tui"), false);
|
||||
assert_eq!(updated, 1, "only the sender's session should be updated");
|
||||
assert!(
|
||||
!sessions[&sid_tui].yolo_mode,
|
||||
@@ -1030,7 +1030,7 @@ async fn drain_respects_deadline() {
|
||||
fn parse_code_nav_capability_present_and_true() {
|
||||
let mut meta = serde_json::Map::new();
|
||||
meta.insert(
|
||||
"x.ai/codeNavigation".to_string(),
|
||||
"kigi/codeNavigation".to_string(),
|
||||
serde_json::json!({ "enabled" : true }),
|
||||
);
|
||||
let init = acp::InitializeRequest::new(acp::ProtocolVersion::V1).client_capabilities(
|
||||
@@ -1054,7 +1054,7 @@ fn parse_code_nav_capability_absent_returns_false() {
|
||||
fn parse_code_nav_capability_false_returns_false() {
|
||||
let mut meta = serde_json::Map::new();
|
||||
meta.insert(
|
||||
"x.ai/codeNavigation".to_string(),
|
||||
"kigi/codeNavigation".to_string(),
|
||||
serde_json::json!({ "enabled" : false }),
|
||||
);
|
||||
let init = acp::InitializeRequest::new(acp::ProtocolVersion::V1).client_capabilities(
|
||||
@@ -1074,18 +1074,18 @@ fn parse_code_nav_capability_false_returns_false() {
|
||||
#[tokio::test]
|
||||
async fn test_per_session_code_nav_isolation() {
|
||||
let web_handle = {
|
||||
let mut h = make_test_handle("model", false, Some("grok-web"));
|
||||
let mut h = make_test_handle("model", false, Some("kigi-web"));
|
||||
h.code_nav_enabled = true;
|
||||
h
|
||||
};
|
||||
let tui_handle = {
|
||||
let mut h = make_test_handle("model", false, Some("grok-tui"));
|
||||
let mut h = make_test_handle("model", false, Some("kigi-tui"));
|
||||
h.code_nav_enabled = false;
|
||||
h
|
||||
};
|
||||
let check = |handle: &crate::session::SessionHandle| {
|
||||
let ct = crate::http::client_type_from_origin(handle.origin_client.as_ref());
|
||||
if !matches!(ct, ClientType::GrokWeb) {
|
||||
if !matches!(ct, ClientType::KigiWeb) {
|
||||
return Err(CodeNavEligibility::ClientNotWeb);
|
||||
}
|
||||
if !handle.code_nav_enabled {
|
||||
@@ -1322,7 +1322,7 @@ async fn resident_activity_reports_needs_input_when_pending() {
|
||||
use crate::agent::roster::RosterActivity;
|
||||
let agent = build_minimal_agent_for_tests();
|
||||
let sid = acp::SessionId::new("sess-pending");
|
||||
let handle = make_test_handle("grok-3", false, None);
|
||||
let handle = make_test_handle("kigi-3", false, None);
|
||||
let pending = handle.pending_interactions.clone();
|
||||
let prompt_id = handle.current_prompt_id.clone();
|
||||
agent.sessions.borrow_mut().insert(sid.clone(), handle);
|
||||
@@ -1339,7 +1339,7 @@ async fn resident_activity_reports_needs_input_when_pending() {
|
||||
pending.lock().unwrap().clear();
|
||||
assert_eq!(agent.resident_activity(&sid), RosterActivity::Working);
|
||||
}
|
||||
/// Drain the agent gateway, returning the first `x.ai/sessions/changed`
|
||||
/// Drain the agent gateway, returning the first `kigi/sessions/changed`
|
||||
/// payload that carries an upserted entry (ignoring any unrelated
|
||||
/// notifications, which parse into an empty `RosterChanged`).
|
||||
fn drain_roster_changed(
|
||||
@@ -1362,7 +1362,7 @@ fn drain_roster_changed(
|
||||
found
|
||||
}
|
||||
/// A turn-boundary activity delta (`push_roster_activity_delta`) broadcasts
|
||||
/// an `x.ai/sessions/changed` upsert carrying the *overridden* activity, so
|
||||
/// an `kigi/sessions/changed` upsert carrying the *overridden* activity, so
|
||||
/// every attached dashboard reflects Working/Idle immediately instead of
|
||||
/// waiting for the ≤1s roster poll (turn-start/turn-end). The
|
||||
/// override matters because at turn-start the actor has not yet published
|
||||
@@ -1384,7 +1384,7 @@ async fn push_roster_activity_delta_broadcasts_overridden_activity() {
|
||||
agent
|
||||
.sessions
|
||||
.borrow_mut()
|
||||
.insert(sid.clone(), make_test_handle("grok-3", false, None));
|
||||
.insert(sid.clone(), make_test_handle("kigi-3", false, None));
|
||||
agent.push_roster_activity_delta(&sid, RosterActivity::Working);
|
||||
let changed = drain_roster_changed(&mut rx).expect("turn-start delta emitted");
|
||||
assert_eq!(changed.upserted.len(), 1);
|
||||
@@ -1427,7 +1427,7 @@ fn check_nav_eligibility_from_sessions(
|
||||
return Err(CodeNavEligibility::SessionRequired);
|
||||
};
|
||||
let ct = crate::http::client_type_from_origin(handle.origin_client.as_ref());
|
||||
if !matches!(ct, ClientType::GrokWeb) {
|
||||
if !matches!(ct, ClientType::KigiWeb) {
|
||||
return Err(CodeNavEligibility::ClientNotWeb);
|
||||
}
|
||||
if !handle.code_nav_enabled {
|
||||
@@ -1442,7 +1442,7 @@ fn check_nav_eligibility_from_sessions(
|
||||
#[tokio::test]
|
||||
async fn test_web_session_with_capability_is_eligible() {
|
||||
let sid = acp::SessionId::new("sess-web");
|
||||
let mut handle = make_test_handle("model", false, Some("grok-web"));
|
||||
let mut handle = make_test_handle("model", false, Some("kigi-web"));
|
||||
handle.code_nav_enabled = true;
|
||||
let sessions = [(sid.clone(), handle)].into();
|
||||
assert!(
|
||||
@@ -1454,7 +1454,7 @@ async fn test_web_session_with_capability_is_eligible() {
|
||||
#[tokio::test]
|
||||
async fn test_tui_session_is_rejected() {
|
||||
let sid = acp::SessionId::new("sess-tui");
|
||||
let mut handle = make_test_handle("model", false, Some("grok-tui"));
|
||||
let mut handle = make_test_handle("model", false, Some("kigi-tui"));
|
||||
handle.code_nav_enabled = true;
|
||||
let sessions = [(sid.clone(), handle)].into();
|
||||
assert_eq!(
|
||||
@@ -1467,7 +1467,7 @@ async fn test_tui_session_is_rejected() {
|
||||
#[tokio::test]
|
||||
async fn test_web_session_without_capability_is_rejected() {
|
||||
let sid = acp::SessionId::new("sess-web-no-cap");
|
||||
let mut handle = make_test_handle("model", false, Some("grok-web"));
|
||||
let mut handle = make_test_handle("model", false, Some("kigi-web"));
|
||||
handle.code_nav_enabled = false;
|
||||
let sessions = [(sid.clone(), handle)].into();
|
||||
assert_eq!(
|
||||
@@ -1482,9 +1482,9 @@ async fn test_web_session_without_capability_is_rejected() {
|
||||
async fn test_leader_mode_two_sessions_stay_isolated() {
|
||||
let web_sid = acp::SessionId::new("web");
|
||||
let tui_sid = acp::SessionId::new("tui");
|
||||
let mut web_handle = make_test_handle("model", false, Some("grok-web"));
|
||||
let mut web_handle = make_test_handle("model", false, Some("kigi-web"));
|
||||
web_handle.code_nav_enabled = true;
|
||||
let mut tui_handle = make_test_handle("model", false, Some("grok-tui"));
|
||||
let mut tui_handle = make_test_handle("model", false, Some("kigi-tui"));
|
||||
tui_handle.code_nav_enabled = false;
|
||||
let sessions = [(web_sid.clone(), web_handle), (tui_sid.clone(), tui_handle)].into();
|
||||
assert!(
|
||||
@@ -1505,7 +1505,7 @@ async fn test_leader_mode_two_sessions_stay_isolated() {
|
||||
#[tokio::test]
|
||||
async fn test_unknown_session_id_returns_session_required() {
|
||||
let known_sid = acp::SessionId::new("known");
|
||||
let mut known_handle = make_test_handle("model", false, Some("grok-web"));
|
||||
let mut known_handle = make_test_handle("model", false, Some("kigi-web"));
|
||||
known_handle.code_nav_enabled = true;
|
||||
let sessions = [(known_sid.clone(), known_handle)].into();
|
||||
let stale_sid = acp::SessionId::new("stale-or-evicted");
|
||||
@@ -1567,7 +1567,7 @@ mod eligibility_gates {
|
||||
code_nav_enabled: bool,
|
||||
indexing_enabled: bool,
|
||||
) -> Result<(), CodeNavEligibility> {
|
||||
if !matches!(client_type, ClientType::GrokWeb) {
|
||||
if !matches!(client_type, ClientType::KigiWeb) {
|
||||
return Err(CodeNavEligibility::ClientNotWeb);
|
||||
}
|
||||
if !code_nav_enabled {
|
||||
@@ -1588,27 +1588,27 @@ mod eligibility_gates {
|
||||
#[test]
|
||||
fn tui_client_rejected() {
|
||||
assert_eq!(
|
||||
check_gates(ClientType::GrokTUI, true, true),
|
||||
check_gates(ClientType::KigiTUI, true, true),
|
||||
Err(CodeNavEligibility::ClientNotWeb)
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn web_client_no_capability_rejected() {
|
||||
assert_eq!(
|
||||
check_gates(ClientType::GrokWeb, false, true),
|
||||
check_gates(ClientType::KigiWeb, false, true),
|
||||
Err(CodeNavEligibility::CapabilityNotAdvertised)
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn web_client_with_capability_config_disabled_rejected() {
|
||||
assert_eq!(
|
||||
check_gates(ClientType::GrokWeb, true, false),
|
||||
check_gates(ClientType::KigiWeb, true, false),
|
||||
Err(CodeNavEligibility::DisabledByConfig)
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn web_client_with_capability_and_config_passes_first_three_gates() {
|
||||
assert!(check_gates(ClientType::GrokWeb, true, true).is_ok());
|
||||
assert!(check_gates(ClientType::KigiWeb, true, true).is_ok());
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
@@ -1672,12 +1672,12 @@ fn write_updates(dir: &std::path::Path, lines: &[&str]) -> PathBuf {
|
||||
}
|
||||
fn bg_line(task_id: &str) -> String {
|
||||
format!(
|
||||
r#"{{"timestamp":1,"method":"_x.ai/session/update","params":{{"sessionId":"s","update":{{"sessionUpdate":"task_backgrounded","task_id":"{task_id}","command":"sleep 99","cwd":"/tmp"}}}}}}"#
|
||||
r#"{{"timestamp":1,"method":"_kigi/session/update","params":{{"sessionId":"s","update":{{"sessionUpdate":"task_backgrounded","task_id":"{task_id}","command":"sleep 99","cwd":"/tmp"}}}}}}"#
|
||||
)
|
||||
}
|
||||
fn completed_line(task_id: &str) -> String {
|
||||
format!(
|
||||
r#"{{"timestamp":2,"method":"_x.ai/session/update","params":{{"sessionId":"s","update":{{"sessionUpdate":"task_completed","task_snapshot":{{"task_id":"{task_id}","completed":true}}}}}}}}"#
|
||||
r#"{{"timestamp":2,"method":"_kigi/session/update","params":{{"sessionId":"s","update":{{"sessionUpdate":"task_completed","task_snapshot":{{"task_id":"{task_id}","completed":true}}}}}}}}"#
|
||||
)
|
||||
}
|
||||
fn orphaned_ids(tasks: &[OrphanedTask]) -> std::collections::HashSet<&str> {
|
||||
@@ -1751,7 +1751,7 @@ fn orphaned_tasks_skips_malformed_lines() {
|
||||
fn orphaned_tasks_ignores_unrelated_updates() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let bg = bg_line("t1");
|
||||
let unrelated = r#"{"timestamp":1,"method":"_x.ai/session/update","params":{"sessionId":"s","update":{"sessionUpdate":"auto_compact_started","percentage":80}}}"#;
|
||||
let unrelated = r#"{"timestamp":1,"method":"_kigi/session/update","params":{"sessionId":"s","update":{"sessionUpdate":"auto_compact_started","percentage":80}}}"#;
|
||||
let path = write_updates(tmp.path(), &[&bg, unrelated]);
|
||||
let result = MvpAgent::find_orphaned_background_tasks(&Some(path));
|
||||
assert_eq!(result.len(), 1);
|
||||
@@ -1761,7 +1761,7 @@ fn orphaned_tasks_filters_rewind_dead_branches() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let user_msg = r#"{"timestamp":0,"method":"session/update","params":{"sessionId":"s","update":{"sessionUpdate":"user_message_chunk","content":{"type":"text","text":"hello"}}}}"#;
|
||||
let bg_before_rewind = bg_line("t-dead");
|
||||
let rewind = r#"{"timestamp":3,"method":"_x.ai/session/update","params":{"sessionId":"s","update":{"sessionUpdate":"rewind_marker","target_prompt_index":0,"created_at":"2025-01-01T00:00:00Z"}}}"#;
|
||||
let rewind = r#"{"timestamp":3,"method":"_kigi/session/update","params":{"sessionId":"s","update":{"sessionUpdate":"rewind_marker","target_prompt_index":0,"created_at":"2025-01-01T00:00:00Z"}}}"#;
|
||||
let user_msg2 = r#"{"timestamp":4,"method":"session/update","params":{"sessionId":"s","update":{"sessionUpdate":"user_message_chunk","content":{"type":"text","text":"retry"}}}}"#;
|
||||
let bg_after_rewind = bg_line("t-alive");
|
||||
let path = write_updates(
|
||||
@@ -1804,7 +1804,7 @@ fn on_demand_enabled_from_remote_settings() {
|
||||
async fn auth_type_session_based_no_current_returns_session_token() {
|
||||
for method_id in [
|
||||
crate::agent::auth_method::CACHED_TOKEN_AUTH_METHOD_ID,
|
||||
crate::agent::auth_method::KIGI_COM_METHOD_ID,
|
||||
crate::agent::auth_method::KIMI_CODE_METHOD_ID,
|
||||
] {
|
||||
let agent = build_minimal_agent_for_tests();
|
||||
agent.set_auth_method(acp::AuthMethodId::new(method_id));
|
||||
@@ -1848,7 +1848,7 @@ async fn auth_type_session_based_with_current_returns_session_token() {
|
||||
use crate::auth::KimiAuth;
|
||||
let agent = build_minimal_agent_for_tests();
|
||||
agent.set_auth_method(acp::AuthMethodId::new(
|
||||
crate::agent::auth_method::KIGI_COM_METHOD_ID,
|
||||
crate::agent::auth_method::KIMI_CODE_METHOD_ID,
|
||||
));
|
||||
agent.auth_manager.hot_swap(KimiAuth::test_default());
|
||||
assert!(agent.auth_manager.current().is_some());
|
||||
@@ -1899,12 +1899,12 @@ async fn cached_token_fallthrough_prefers_api_key_for_deployment_key() {
|
||||
);
|
||||
}
|
||||
/// No advertiseable credentials at all (no env key, no kill switch): the user
|
||||
/// genuinely needs to log in, so the fallthrough is interactive `grok.com`.
|
||||
/// genuinely needs to log in, so the fallthrough is interactive `kimi-code`.
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
#[serial_test::serial]
|
||||
async fn cached_token_fallthrough_falls_to_grok_com_without_credentials() {
|
||||
async fn cached_token_fallthrough_falls_to_kigi_com_without_credentials() {
|
||||
use crate::agent::auth_method::{
|
||||
KIGI_COM_METHOD_ID, LEGACY_XAI_API_KEY_ENV_VAR, XAI_API_KEY_ENV_VAR,
|
||||
KIMI_CODE_METHOD_ID, LEGACY_XAI_API_KEY_ENV_VAR, XAI_API_KEY_ENV_VAR,
|
||||
};
|
||||
use kigi_test_support::EnvGuard;
|
||||
let _lockdown = EnvGuard::unset("KIGI_DISABLE_API_KEY_AUTH");
|
||||
@@ -1913,8 +1913,8 @@ async fn cached_token_fallthrough_falls_to_grok_com_without_credentials() {
|
||||
let agent = build_minimal_agent_for_tests();
|
||||
assert_eq!(
|
||||
agent.cached_token_fallthrough_method_id().0.as_ref(),
|
||||
KIGI_COM_METHOD_ID,
|
||||
"no API-key creds and no kill switch -> interactive grok.com login",
|
||||
KIMI_CODE_METHOD_ID,
|
||||
"no API-key creds and no kill switch -> interactive kimi.com login",
|
||||
);
|
||||
}
|
||||
/// `parse_session_kind` routes `session/load` to the gateway Chat path vs. the
|
||||
@@ -1926,22 +1926,22 @@ fn parse_session_kind_matrix() {
|
||||
let cases: &[(&str, serde_json::Value, SessionKind)] = &[
|
||||
(
|
||||
"chat",
|
||||
json!({ "x.ai/session" : { "kind" : "chat" } }),
|
||||
json!({ "kigi/session" : { "kind" : "chat" } }),
|
||||
SessionKind::Chat,
|
||||
),
|
||||
(
|
||||
"build",
|
||||
json!({ "x.ai/session" : { "kind" : "build" } }),
|
||||
json!({ "kigi/session" : { "kind" : "build" } }),
|
||||
SessionKind::Build,
|
||||
),
|
||||
(
|
||||
"chat_malformed_sibling",
|
||||
json!({ "x.ai/session" : { "kind" : "chat", "facets" : "not-a-map" } }),
|
||||
json!({ "kigi/session" : { "kind" : "chat", "facets" : "not-a-map" } }),
|
||||
SessionKind::Chat,
|
||||
),
|
||||
(
|
||||
"unknown_kind",
|
||||
json!({ "x.ai/session" : { "kind" : "frob" } }),
|
||||
json!({ "kigi/session" : { "kind" : "frob" } }),
|
||||
SessionKind::Build,
|
||||
),
|
||||
("absent", json!({}), SessionKind::Build),
|
||||
@@ -1954,9 +1954,9 @@ fn parse_session_kind_matrix() {
|
||||
#[test]
|
||||
fn chat_initial_model_matrix() {
|
||||
let cases: &[(&str, bool, Option<&str>, Option<&str>)] = &[
|
||||
("chat_with_model", true, Some("grok-4.5"), Some("grok-4.5")),
|
||||
("chat_with_model", true, Some("kigi-4.5"), Some("kigi-4.5")),
|
||||
("chat_without_model", true, None, None),
|
||||
("build_with_model", false, Some("grok-4.5"), None),
|
||||
("build_with_model", false, Some("kigi-4.5"), None),
|
||||
("build_without_model", false, None, None),
|
||||
];
|
||||
for (label, is_chat_kind, custom_model_id, expected) in cases {
|
||||
@@ -1983,27 +1983,27 @@ fn chat_new_session_model_state_matrix() {
|
||||
let cases: &[(&str, acp::SessionModelState, Option<&str>, &str)] = &[
|
||||
(
|
||||
"requested_in_catalog",
|
||||
state_with("auto", &["auto", "grok-4"]),
|
||||
Some("grok-4"),
|
||||
"grok-4",
|
||||
state_with("auto", &["auto", "kigi-4"]),
|
||||
Some("kigi-4"),
|
||||
"kigi-4",
|
||||
),
|
||||
(
|
||||
"no_request_keeps_catalog_default",
|
||||
state_with("auto", &["auto", "grok-4"]),
|
||||
state_with("auto", &["auto", "kigi-4"]),
|
||||
None,
|
||||
"auto",
|
||||
),
|
||||
(
|
||||
"requested_not_in_catalog",
|
||||
state_with("auto", &["auto"]),
|
||||
Some("grok-4.5"),
|
||||
"grok-4.5",
|
||||
Some("kigi-4.5"),
|
||||
"kigi-4.5",
|
||||
),
|
||||
(
|
||||
"requested_with_empty_catalog",
|
||||
state_with("", &[]),
|
||||
Some("grok-4"),
|
||||
"grok-4",
|
||||
Some("kigi-4"),
|
||||
"kigi-4",
|
||||
),
|
||||
];
|
||||
for (label, state, requested, expected) in cases {
|
||||
@@ -2069,7 +2069,7 @@ fn ext_method_rewind_uses_local_dispatch_without_bridge() {
|
||||
let params = serde_json::json!({ "sessionId" : "sess-local" });
|
||||
let err = agent
|
||||
.ext_method(acp::ExtRequest::new(
|
||||
"x.ai/rewind/points",
|
||||
"kigi/rewind/points",
|
||||
std::sync::Arc::from(serde_json::value::to_raw_value(¶ms).unwrap()),
|
||||
))
|
||||
.await
|
||||
@@ -2116,7 +2116,7 @@ fn make_live_session_handle(
|
||||
tokio::sync::mpsc::UnboundedReceiver<TestSessionCommand>,
|
||||
) {
|
||||
let (cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let mut handle = make_test_handle("test-model", false, Some("grok-tui"));
|
||||
let mut handle = make_test_handle("test-model", false, Some("kigi-tui"));
|
||||
handle.cmd_tx = cmd_tx.clone();
|
||||
handle.info = crate::session::info::Info {
|
||||
id: sid.clone(),
|
||||
@@ -2149,14 +2149,14 @@ fn spawn_fake_actor(
|
||||
});
|
||||
observed_rx
|
||||
}
|
||||
/// Drive `x.ai/internal/evict_sessions` through the real `ext_notification`
|
||||
/// Drive `kigi/internal/evict_sessions` through the real `ext_notification`
|
||||
/// handler path (not the internal helper) — matches how the leader server
|
||||
/// signals a client disconnect.
|
||||
async fn drive_disconnect(agent: &MvpAgent, sid: &acp::SessionId) {
|
||||
drive_disconnect_many(agent, &[sid]).await;
|
||||
}
|
||||
/// Like `drive_disconnect`, but evicts several sessions in a single
|
||||
/// `x.ai/internal/evict_sessions` notification — the realistic shape of a
|
||||
/// `kigi/internal/evict_sessions` notification — the realistic shape of a
|
||||
/// real client disconnect, and the path that exercises `handle_evict_sessions`'
|
||||
/// concurrent `join_all` check pass followed by the sequential act pass.
|
||||
async fn drive_disconnect_many(agent: &MvpAgent, sids: &[&acp::SessionId]) {
|
||||
@@ -2166,13 +2166,13 @@ async fn drive_disconnect_many(agent: &MvpAgent, sids: &[&acp::SessionId]) {
|
||||
let raw = serde_json::value::to_raw_value(¶ms).unwrap();
|
||||
agent
|
||||
.ext_notification(acp::ExtNotification::new(
|
||||
"x.ai/internal/evict_sessions",
|
||||
"kigi/internal/evict_sessions",
|
||||
raw.into(),
|
||||
))
|
||||
.await
|
||||
.expect("evict_sessions notification must be handled");
|
||||
}
|
||||
/// Drive `x.ai/session/close` through the real `ext_method` dispatch
|
||||
/// Drive `kigi/session/close` through the real `ext_method` dispatch
|
||||
/// (`ext_method` → `handlers::session::handle` → `handle_session_close`),
|
||||
/// exercising the exact production path that finalizes the replica.
|
||||
async fn drive_close(agent: &MvpAgent, session_id: &str) -> Result<acp::ExtResponse, acp::Error> {
|
||||
@@ -2181,7 +2181,7 @@ async fn drive_close(agent: &MvpAgent, session_id: &str) -> Result<acp::ExtRespo
|
||||
let raw = serde_json::value::to_raw_value(¶ms).unwrap();
|
||||
agent
|
||||
.ext_method(acp::ExtRequest::new(
|
||||
"x.ai/session/close",
|
||||
"kigi/session/close",
|
||||
std::sync::Arc::from(raw),
|
||||
))
|
||||
.await
|
||||
@@ -2423,7 +2423,7 @@ fn disconnect_keeps_resident_when_plan_approval_parked() {
|
||||
);
|
||||
});
|
||||
}
|
||||
/// Mixed batch in a *single* `x.ai/internal/evict_sessions` notification —
|
||||
/// Mixed batch in a *single* `kigi/internal/evict_sessions` notification —
|
||||
/// the realistic disconnect shape and the path that exercises
|
||||
/// `handle_evict_sessions`' `join_all` two-pass (concurrent `IsBusy` checks,
|
||||
/// then sequential act). One session's actor reports busy (→ kept resident,
|
||||
@@ -2512,7 +2512,7 @@ fn session_live_state_map_is_bounded_across_cycles() {
|
||||
});
|
||||
}
|
||||
/// Finalize fires on a genuine terminal close — driven through the **real**
|
||||
/// `x.ai/session/close` dispatch (`ext_method` → `handle_session_close`),
|
||||
/// `kigi/session/close` dispatch (`ext_method` → `handle_session_close`),
|
||||
/// not the internal helper. Proves finalize was *moved* (not removed) and
|
||||
/// guards the handler's `existed` gate. (Finalize assertion is
|
||||
/// invocation-level; see note in `finalize_session_replica`.)
|
||||
@@ -2663,7 +2663,7 @@ fn reload_after_terminal_removal_starts_clean() {
|
||||
}
|
||||
/// Build an agent whose gateway is wired to a live receiver, so a test can
|
||||
/// observe (and answer) agent→client reverse-requests like the dormant
|
||||
/// `x.ai/folder_trust/request` round-trip.
|
||||
/// `kigi/folder_trust/request` round-trip.
|
||||
fn build_agent_with_gateway_rx() -> (
|
||||
MvpAgent,
|
||||
tokio::sync::mpsc::UnboundedReceiver<kigi_acp_lib::AcpClientMessage>,
|
||||
@@ -2698,7 +2698,7 @@ fn folder_trust_on() -> crate::util::config::RemoteSettings {
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
/// Pull the next `x.ai/folder_trust/request` reverse-request off the gateway and
|
||||
/// Pull the next `kigi/folder_trust/request` reverse-request off the gateway and
|
||||
/// answer it with `outcome`. Returns the request's decoded params.
|
||||
async fn answer_folder_trust_request(
|
||||
gw_rx: &mut tokio::sync::mpsc::UnboundedReceiver<kigi_acp_lib::AcpClientMessage>,
|
||||
@@ -2711,7 +2711,7 @@ async fn answer_folder_trust_request(
|
||||
let kigi_acp_lib::AcpClientMessage::ExtMethod(args) = msg else {
|
||||
panic!("expected an ext_method reverse-request, got a different message");
|
||||
};
|
||||
assert_eq!(args.request.method.as_ref(), "x.ai/folder_trust/request");
|
||||
assert_eq!(args.request.method.as_ref(), "kigi/folder_trust/request");
|
||||
let params: serde_json::Value = serde_json::from_str(args.request.params.get()).unwrap();
|
||||
let resp: acp::ExtResponse = acp::ExtResponse::new(std::sync::Arc::from(
|
||||
serde_json::value::to_raw_value(&serde_json::json!({ "outcome" : outcome })).unwrap(),
|
||||
@@ -3142,21 +3142,21 @@ mod direct_hub_cloud_removed {
|
||||
}
|
||||
#[test]
|
||||
fn cloud_server_id_meta_is_hard_error() {
|
||||
let meta = serde_json::json!({ "x.ai/cloud_server_id" : "srv-123" });
|
||||
let meta = serde_json::json!({ "kigi/cloud_server_id" : "srv-123" });
|
||||
let err = reject_direct_hub_cloud_meta(meta.as_object()).expect_err("must reject");
|
||||
assert_direct_hub_error(err);
|
||||
}
|
||||
#[test]
|
||||
fn cloud_server_id_null_still_present_is_hard_error() {
|
||||
let meta = serde_json::json!({ "x.ai/cloud_server_id" : null });
|
||||
let meta = serde_json::json!({ "kigi/cloud_server_id" : null });
|
||||
let err = reject_direct_hub_cloud_meta(meta.as_object()).expect_err("must reject");
|
||||
assert_direct_hub_error(err);
|
||||
}
|
||||
#[test]
|
||||
fn cloud_server_id_with_gateway_meta_still_hard_error() {
|
||||
let meta = serde_json::json!(
|
||||
{ "x.ai/cloud_server_id" : "srv-legacy", "envId" : "env-1",
|
||||
"x.ai/cloud_existing_workspace" : { "server_id" : "ws-1", "cwd" :
|
||||
{ "kigi/cloud_server_id" : "srv-legacy", "envId" : "env-1",
|
||||
"kigi/cloud_existing_workspace" : { "server_id" : "ws-1", "cwd" :
|
||||
"/workspace" } }
|
||||
);
|
||||
let err = reject_direct_hub_cloud_meta(meta.as_object()).expect_err("Direct stamp wins");
|
||||
@@ -3173,7 +3173,7 @@ mod direct_hub_cloud_removed {
|
||||
assert!(
|
||||
reject_direct_hub_cloud_meta(
|
||||
serde_json::json!({
|
||||
"x.ai/cloud_existing_workspace" : { "server_id" : "ws-1", "cwd" :
|
||||
"kigi/cloud_existing_workspace" : { "server_id" : "ws-1", "cwd" :
|
||||
"/workspace" } })
|
||||
.as_object()
|
||||
)
|
||||
@@ -3212,7 +3212,7 @@ mod soft_default_settings_emit {
|
||||
let kigi_acp_lib::AcpClientMessage::ExtNotification(args) = msg else {
|
||||
panic!("expected ExtNotification, got {msg:?}");
|
||||
};
|
||||
assert_eq!(args.request.method.as_ref(), "x.ai/settings/update");
|
||||
assert_eq!(args.request.method.as_ref(), "kigi/settings/update");
|
||||
let params: serde_json::Value =
|
||||
serde_json::from_str(args.request.params.get()).expect("parse params");
|
||||
assert_eq!(
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
//! actors) plus recently-touched on-disk (`Dormant`) sessions. Clients read it
|
||||
//! two ways:
|
||||
//!
|
||||
//! - request/response `x.ai/sessions/list` → `{ "sessions": [RosterEntry, …] }`
|
||||
//! - broadcast notification `x.ai/sessions/changed` →
|
||||
//! - request/response `kigi/sessions/list` → `{ "sessions": [RosterEntry, …] }`
|
||||
//! - broadcast notification `kigi/sessions/changed` →
|
||||
//! `{ "upserted": [RosterEntry, …], "removed": ["sess-abc", …] }`
|
||||
//!
|
||||
//! The wire shape is intentionally small and current-state only — no event
|
||||
@@ -76,13 +76,13 @@ pub struct RosterEntry {
|
||||
pub origin: RosterOrigin,
|
||||
}
|
||||
|
||||
/// Response payload for `x.ai/sessions/list`.
|
||||
/// Response payload for `kigi/sessions/list`.
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||
pub struct RosterListResponse {
|
||||
pub sessions: Vec<RosterEntry>,
|
||||
}
|
||||
|
||||
/// Params payload for the `x.ai/sessions/changed` broadcast notification.
|
||||
/// Params payload for the `kigi/sessions/changed` broadcast notification.
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||
pub struct RosterChanged {
|
||||
#[serde(default)]
|
||||
@@ -92,8 +92,8 @@ pub struct RosterChanged {
|
||||
}
|
||||
|
||||
/// JSON-RPC method names for the roster API.
|
||||
pub const SESSIONS_LIST_METHOD: &str = "x.ai/sessions/list";
|
||||
pub const SESSIONS_CHANGED_METHOD: &str = "x.ai/sessions/changed";
|
||||
pub const SESSIONS_LIST_METHOD: &str = "kigi/sessions/list";
|
||||
pub const SESSIONS_CHANGED_METHOD: &str = "kigi/sessions/changed";
|
||||
|
||||
/// Merge live `resident` rows with on-disk `summaries` into the sorted roster.
|
||||
/// Pure, so it is unit-testable without disk or a live actor.
|
||||
@@ -173,7 +173,7 @@ mod merge_roster_tests {
|
||||
title: None,
|
||||
cwd: format!("/live/{id}"),
|
||||
is_worktree: false,
|
||||
model_id: Some("grok-4".into()),
|
||||
model_id: Some("kigi-4".into()),
|
||||
reasoning_effort: None,
|
||||
yolo: false,
|
||||
activity,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! WebSocket server for remote agent connections.
|
||||
//!
|
||||
//! This module provides a WebSocket server that allows remote TUI clients to
|
||||
//! connect to a grok agent running on a different machine.
|
||||
//! connect to a kigi agent running on a different machine.
|
||||
//!
|
||||
//! The agent persists across WebSocket reconnections: a single MvpAgent instance
|
||||
//! is created on first connection and reused for all subsequent connections. This
|
||||
|
||||
@@ -25,7 +25,7 @@ pub struct SessionConfigOption {
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GrokSessionDetail {
|
||||
pub struct KigiSessionDetail {
|
||||
pub session_id: String,
|
||||
pub kind: String,
|
||||
pub cwd: String,
|
||||
@@ -34,7 +34,7 @@ pub struct GrokSessionDetail {
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
impl GrokSessionDetail {
|
||||
impl KigiSessionDetail {
|
||||
pub fn build(
|
||||
session_id: String,
|
||||
cwd: String,
|
||||
@@ -124,11 +124,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn options_have_one_selected_model_and_a_mode_per_effort() {
|
||||
let models = [
|
||||
model("grok-build", "Grok Build"),
|
||||
model("grok-4.5", "Grok 4.5"),
|
||||
];
|
||||
let current = acp::ModelId::from("grok-build");
|
||||
let models = [model("kigi", "Kigi"), model("kigi-4.5", "Kigi 4.5")];
|
||||
let current = acp::ModelId::from("kigi");
|
||||
let opts = build_session_config_options(
|
||||
&models,
|
||||
¤t,
|
||||
@@ -140,7 +137,7 @@ mod tests {
|
||||
assert_eq!(model_opts.len(), 2);
|
||||
let selected_models: Vec<_> = model_opts.iter().filter(|o| o.selected).collect();
|
||||
assert_eq!(selected_models.len(), 1);
|
||||
assert_eq!(selected_models[0].id, "grok-build");
|
||||
assert_eq!(selected_models[0].id, "kigi");
|
||||
|
||||
let mode_opts: Vec<_> = opts.iter().filter(|o| o.category == "mode").collect();
|
||||
assert_eq!(mode_opts.len(), SELECTABLE_REASONING_EFFORTS.len());
|
||||
@@ -153,8 +150,8 @@ mod tests {
|
||||
#[test]
|
||||
fn none_effort_is_not_a_user_selectable_mode() {
|
||||
assert!(!SELECTABLE_REASONING_EFFORTS.contains(&ReasoningEffort::None));
|
||||
let models = [model("grok-build", "Grok Build")];
|
||||
let current = acp::ModelId::from("grok-build");
|
||||
let models = [model("kigi", "Kigi")];
|
||||
let current = acp::ModelId::from("kigi");
|
||||
let opts = build_session_config_options(
|
||||
&models,
|
||||
¤t,
|
||||
@@ -168,8 +165,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn no_mode_options_when_model_lacks_effort_support() {
|
||||
let models = [model("grok-build", "Grok Build")];
|
||||
let current = acp::ModelId::from("grok-build");
|
||||
let models = [model("kigi", "Kigi")];
|
||||
let current = acp::ModelId::from("kigi");
|
||||
let opts = build_session_config_options(&models, ¤t, &[], None);
|
||||
assert_eq!(opts.len(), 1);
|
||||
assert!(opts.iter().all(|o| o.category == "model"));
|
||||
@@ -177,42 +174,42 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn model_label_falls_back_to_id_when_name_empty() {
|
||||
let models = [model("grok-build", "")];
|
||||
let current = acp::ModelId::from("grok-build");
|
||||
let models = [model("kigi", "")];
|
||||
let current = acp::ModelId::from("kigi");
|
||||
let opts = build_session_config_options(&models, ¤t, &[], None);
|
||||
assert_eq!(opts[0].label, "grok-build");
|
||||
assert_eq!(opts[0].label, "kigi");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_config_option_serializes_camel_case() {
|
||||
let opt = SessionConfigOption {
|
||||
id: "grok-build".to_string(),
|
||||
id: "kigi".to_string(),
|
||||
category: "model".to_string(),
|
||||
label: "Grok Build".to_string(),
|
||||
label: "Kigi".to_string(),
|
||||
description: None,
|
||||
selected: true,
|
||||
};
|
||||
let v = serde_json::to_value(&opt).expect("serialize");
|
||||
assert_eq!(v["id"], "grok-build");
|
||||
assert_eq!(v["id"], "kigi");
|
||||
assert_eq!(v["category"], "model");
|
||||
assert_eq!(v["label"], "Grok Build");
|
||||
assert_eq!(v["label"], "Kigi");
|
||||
assert_eq!(v["selected"], true);
|
||||
assert!(v.get("description").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grok_session_detail_serializes_camel_case() {
|
||||
let detail = GrokSessionDetail::build(
|
||||
fn kigi_session_detail_serializes_camel_case() {
|
||||
let detail = KigiSessionDetail::build(
|
||||
"sess-1".to_string(),
|
||||
"/Users/me/xai".to_string(),
|
||||
"grok-build".to_string(),
|
||||
"kigi".to_string(),
|
||||
None,
|
||||
);
|
||||
let v = serde_json::to_value(&detail).expect("serialize");
|
||||
assert_eq!(v["sessionId"], "sess-1");
|
||||
assert_eq!(v["kind"], "build");
|
||||
assert_eq!(v["cwd"], "/Users/me/xai");
|
||||
assert_eq!(v["currentModelId"], "grok-build");
|
||||
assert_eq!(v["currentModelId"], "kigi");
|
||||
assert!(v.get("title").is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::session::{
|
||||
use crate::terminal::AsyncTerminalRunner;
|
||||
use crate::tools::ToolContext;
|
||||
use kigi_acp_lib::AcpAgentGatewaySender as GatewaySender;
|
||||
use kigi_tools::implementations::grok_build::task::types::*;
|
||||
use kigi_tools::implementations::kigi::task::types::*;
|
||||
use kigi_workspace::file_system::AsyncFileSystem;
|
||||
use kigi_hunk_tracker::HunkTrackerHandle;
|
||||
use super::*;
|
||||
@@ -156,8 +156,8 @@ impl SubagentCoordinator {
|
||||
pub fn outstanding_reply_for_prompt(
|
||||
&self,
|
||||
prompt_id: &str,
|
||||
) -> kigi_tools::implementations::grok_build::task::types::SubagentOutstandingReply {
|
||||
kigi_tools::implementations::grok_build::task::types::SubagentOutstandingReply {
|
||||
) -> kigi_tools::implementations::kigi::task::types::SubagentOutstandingReply {
|
||||
kigi_tools::implementations::kigi::task::types::SubagentOutstandingReply {
|
||||
live_ids: self.outstanding_for_prompt(prompt_id),
|
||||
background_live: self.background_live_for_prompt(prompt_id),
|
||||
subagent_usage_not_applied: self.subagent_usage_not_applied(prompt_id),
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::session::{
|
||||
use crate::terminal::AsyncTerminalRunner;
|
||||
use crate::tools::ToolContext;
|
||||
use kigi_acp_lib::AcpAgentGatewaySender as GatewaySender;
|
||||
use kigi_tools::implementations::grok_build::task::types::*;
|
||||
use kigi_tools::implementations::kigi::task::types::*;
|
||||
use kigi_workspace::file_system::AsyncFileSystem;
|
||||
use kigi_hunk_tracker::HunkTrackerHandle;
|
||||
use super::*;
|
||||
@@ -323,7 +323,7 @@ impl SubagentCoordinator {
|
||||
/// asynchronously after dropping the coordinator borrow.
|
||||
///
|
||||
/// Returns an empty `Vec` if no active subagents match the given
|
||||
/// parent session ID. Callers (e.g. the `x.ai/subagent/list_running`
|
||||
/// parent session ID. Callers (e.g. the `kigi/subagent/list_running`
|
||||
/// ACP handler) should treat an empty result as a normal "no running
|
||||
/// subagents" response, not an error.
|
||||
pub(crate) fn list_running_for_parent(
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::session::{
|
||||
use crate::terminal::AsyncTerminalRunner;
|
||||
use crate::tools::ToolContext;
|
||||
use kigi_acp_lib::AcpAgentGatewaySender as GatewaySender;
|
||||
use kigi_tools::implementations::grok_build::task::types::*;
|
||||
use kigi_tools::implementations::kigi::task::types::*;
|
||||
use kigi_workspace::file_system::AsyncFileSystem;
|
||||
use kigi_hunk_tracker::HunkTrackerHandle;
|
||||
use super::*;
|
||||
@@ -306,7 +306,7 @@ pub(crate) async fn handle_subagent_request(
|
||||
subagent_id = % request.id, error = % e,
|
||||
"Could not resolve worktree base dir, using temp dir for subagent worktree"
|
||||
);
|
||||
std::env::temp_dir().join("grok-subagent-worktrees").join(&request.id)
|
||||
std::env::temp_dir().join("kigi-subagent-worktrees").join(&request.id)
|
||||
}
|
||||
};
|
||||
let source_clone = source_cwd;
|
||||
@@ -396,7 +396,7 @@ pub(crate) async fn handle_subagent_request(
|
||||
);
|
||||
}
|
||||
{
|
||||
use kigi_tools::implementations::grok_build::task::MAX_SUBAGENT_DEPTH;
|
||||
use kigi_tools::implementations::kigi::task::MAX_SUBAGENT_DEPTH;
|
||||
use kigi_tools::types::tool::ToolKind;
|
||||
let child_depth = ctx.parent_depth + 1;
|
||||
if child_depth >= MAX_SUBAGENT_DEPTH {
|
||||
@@ -754,10 +754,10 @@ pub(crate) async fn handle_subagent_request(
|
||||
}
|
||||
}
|
||||
if let Some(scope) = agent_memory_scope {
|
||||
use kigi_tools::implementations::grok_build;
|
||||
use kigi_tools::implementations::kigi;
|
||||
use kigi_tools::implementations::opencode;
|
||||
let memory_tools: Vec<kigi_tools::registry::types::ToolConfig> = vec![
|
||||
(& grok_build::ReadFileTool).into(), (& grok_build::SearchReplaceTool)
|
||||
(& kigi::ReadFileTool).into(), (& kigi::SearchReplaceTool)
|
||||
.into(), (& opencode::OpenCodeWriteTool).into(),
|
||||
];
|
||||
for tc in memory_tools {
|
||||
@@ -1570,7 +1570,7 @@ pub(crate) async fn handle_subagent_request(
|
||||
let mut worktree_removed = false;
|
||||
if let Some(ref wt_path) = worktree_path {
|
||||
if snapshot_dispose_enabled {
|
||||
let ref_name = format!("refs/grok/subagents/{}", request.id);
|
||||
let ref_name = format!("refs/kigi/subagents/{}", request.id);
|
||||
let source_repo = resolve_subagent_source_repo(&ctx);
|
||||
match crate::session::worktree::snapshot_subagent_worktree(
|
||||
wt_path,
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::tools::ToolContext;
|
||||
use agent_client_protocol as acp;
|
||||
use kigi_acp_lib::AcpAgentGatewaySender as GatewaySender;
|
||||
use kigi_hunk_tracker::HunkTrackerHandle;
|
||||
use kigi_tools::implementations::grok_build::task::types::*;
|
||||
use kigi_tools::implementations::kigi::task::types::*;
|
||||
use kigi_workspace::file_system::AsyncFileSystem;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -197,7 +197,7 @@ pub(crate) struct SubagentSpawnContext {
|
||||
/// Parent's scheduler handle. When `Some`, the subagent reuses the
|
||||
/// parent's scheduler actor so scheduled tasks survive subagent exit.
|
||||
pub parent_scheduler_handle:
|
||||
Option<kigi_tools::implementations::grok_build::scheduler::types::SchedulerHandle>,
|
||||
Option<kigi_tools::implementations::kigi::scheduler::types::SchedulerHandle>,
|
||||
/// Parent's session environment variables (.envrc + color settings).
|
||||
/// Shared so the child inherits the same env without re-loading.
|
||||
pub session_env: Arc<HashMap<String, String>>,
|
||||
@@ -207,10 +207,10 @@ pub(crate) struct SubagentSpawnContext {
|
||||
/// Resolved sampling config for web_search.
|
||||
pub web_search_config: kigi_tools::implementations::WebSearchConfig,
|
||||
/// Resolved config for web fetch.
|
||||
pub web_fetch_config: kigi_tools::implementations::grok_build::web_fetch::WebFetchConfig,
|
||||
pub web_fetch_config: kigi_tools::implementations::kigi::web_fetch::WebFetchConfig,
|
||||
/// Resolved config for the deploy service.
|
||||
pub app_builder_deployer_config:
|
||||
kigi_tools::implementations::grok_build::deploy_app::AppBuilderDeployerConfig,
|
||||
kigi_tools::implementations::kigi::deploy_app::AppBuilderDeployerConfig,
|
||||
/// Whether the write_file tool is enabled.
|
||||
pub write_file_enabled: bool,
|
||||
/// Whether goal mode (`/goal`) is enabled.
|
||||
@@ -301,7 +301,7 @@ pub(crate) struct SubagentSpawnContext {
|
||||
/// goes through `agent/config.rs::sampling_config_for_model`
|
||||
/// which always sets that field to `None`.
|
||||
pub attribution_callback: Option<kigi_sampler::SharedAttributionCallback>,
|
||||
/// Parent session's agent name (e.g. "grok-build").
|
||||
/// Parent session's agent name (e.g. "kigi").
|
||||
pub parent_agent_name: Option<String>,
|
||||
/// `agent_type` of the parent's current model — the harness-flavor fallback
|
||||
/// when `parent_agent_name` is not a recognized harness, e.g. a custom
|
||||
@@ -1639,12 +1639,12 @@ pub(crate) fn subagent_harness_flavor_is_representable(_agent_type: &str) -> boo
|
||||
/// Apply the harness-dependent toolset/prompt re-selection to a resolved
|
||||
/// agent definition.
|
||||
///
|
||||
/// The harness flavor (alternate vs grok-build) normally follows the PARENT
|
||||
/// agent: `GrokBuildOrchestrator` parents give children
|
||||
/// The harness flavor (alternate vs kigi) normally follows the PARENT
|
||||
/// agent: `KigiOrchestrator` parents give children
|
||||
/// the alternate harness; the orchestrator keeps children lean, and other parents
|
||||
/// inherit the file-tool override (hashline vs standard). A `/goal` role may
|
||||
/// pass `harness_agent_type` to OVERRIDE that flavor regardless of the parent
|
||||
/// (so a grok-build session can run an alternate-harness verifier and vice-versa);
|
||||
/// (so a kigi session can run an alternate-harness verifier and vice-versa);
|
||||
/// `None` for every non-goal spawn ⇒ the parent decides (unchanged). The base
|
||||
/// toolset stays role-dependent on `subagent_type` (general-purpose →
|
||||
/// implementer, else explorer), so the role keeps a capable toolset on the
|
||||
@@ -2170,7 +2170,7 @@ fn emit_subagent_notification(
|
||||
.ok();
|
||||
if let Some(params) = params {
|
||||
let ext_notification =
|
||||
acp::ExtNotification::new("x.ai/session_notification", params.into());
|
||||
acp::ExtNotification::new("kigi/session_notification", params.into());
|
||||
gateway.forward_fire_and_forget(ext_notification);
|
||||
}
|
||||
}
|
||||
@@ -2224,7 +2224,7 @@ fn goal_tick_cmd_tx(
|
||||
///
|
||||
/// Notifications are **not** persisted to JSONL — they are transient UI
|
||||
/// hints, not authoritative lifecycle events. The TUI can resync via
|
||||
/// `x.ai/subagent/list_running` on reconnect.
|
||||
/// `kigi/subagent/list_running` on reconnect.
|
||||
fn spawn_progress_publisher(
|
||||
signals_handle: crate::session::signals::SessionSignalsHandle,
|
||||
gateway: GatewaySender,
|
||||
@@ -2289,7 +2289,7 @@ fn spawn_progress_publisher(
|
||||
}
|
||||
if let Some(params) = params {
|
||||
let ext_notification =
|
||||
acp::ExtNotification::new("x.ai/session_notification", params.into());
|
||||
acp::ExtNotification::new("kigi/session_notification", params.into());
|
||||
gateway.forward_fire_and_forget(ext_notification);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,11 +119,11 @@ fn subagent_max_turns_definition_wins_else_inherits_parent() {
|
||||
fn resume_worktree_action_covers_three_outcomes() {
|
||||
use super::{ResumeWorktreeAction, resume_worktree_action};
|
||||
assert_eq!(
|
||||
resume_worktree_action(true, Some("refs/grok/subagents/x")),
|
||||
resume_worktree_action(true, Some("refs/kigi/subagents/x")),
|
||||
ResumeWorktreeAction::Rehydrate
|
||||
);
|
||||
assert_eq!(
|
||||
resume_worktree_action(false, Some("refs/grok/subagents/x")),
|
||||
resume_worktree_action(false, Some("refs/kigi/subagents/x")),
|
||||
ResumeWorktreeAction::Rehydrate
|
||||
);
|
||||
assert_eq!(resume_worktree_action(true, None), ResumeWorktreeAction::Reuse);
|
||||
@@ -1099,7 +1099,7 @@ fn dummy_tracker(
|
||||
force_compact: Arc::new(AtomicBool::new(false)),
|
||||
permission_handle: kigi_workspace::permission::PermissionHandle::allow_all(),
|
||||
attribution_callback: None,
|
||||
agent_name: "grok-build".to_string(),
|
||||
agent_name: "kigi".to_string(),
|
||||
session_default_agent_profile: None,
|
||||
allowed_subagent_types: None,
|
||||
hook_registry: None,
|
||||
@@ -1938,7 +1938,7 @@ async fn bootstrap_fork_live_parent_chat_state_is_forked_with_marker() {
|
||||
const MARKER: &str = "UNIQUE_LIVE_FORK_MARKER_xyz789";
|
||||
let req = bootstrap_test_request(true);
|
||||
let mut ctx = ctx_with_toggle(HashMap::new());
|
||||
let chat = spawn_test_parent_chat_state("grok-4.5");
|
||||
let chat = spawn_test_parent_chat_state("kigi-4.5");
|
||||
chat.replace_conversation(
|
||||
vec![
|
||||
ConversationItem::system("parent system"),
|
||||
@@ -2127,7 +2127,7 @@ async fn handle_subagent_request_rejects_nonexistent_cwd() {
|
||||
#[tokio::test]
|
||||
async fn handle_subagent_request_rejects_file_as_cwd() {
|
||||
let tmp_dir = tempfile::TempDir::new().unwrap();
|
||||
let tmp_file = tmp_dir.path().join("grok-test-cwd-file");
|
||||
let tmp_file = tmp_dir.path().join("kigi-test-cwd-file");
|
||||
std::fs::write(&tmp_file, b"not a directory").unwrap();
|
||||
let ctx = ctx_with_toggle(HashMap::new());
|
||||
let coordinator = std::cell::RefCell::new(SubagentCoordinator::new());
|
||||
@@ -2359,7 +2359,7 @@ fn subagent_await_budget_default_and_override() {
|
||||
fn summarize_tool_config_uses_name_override_and_strips_namespace() {
|
||||
use kigi_tools::registry::types::{ToolConfig, ToolServerConfig};
|
||||
use kigi_tools::types::tool::ToolKind;
|
||||
let mut read = ToolConfig::from_id("GrokBuild:read_file");
|
||||
let mut read = ToolConfig::from_id("Kigi:read_file");
|
||||
read.kind = Some(ToolKind::Read);
|
||||
let mut read_dup = ToolConfig::from_id("Codex:read_file");
|
||||
read_dup.kind = Some(ToolKind::Read);
|
||||
@@ -2410,7 +2410,7 @@ fn describe_subagent_type_not_allowed_outside_allow_list() {
|
||||
other => panic!("expected NotAllowed, got {other:?}"),
|
||||
}
|
||||
}
|
||||
/// Regression: on the DEFAULT grok-build host —
|
||||
/// Regression: on the DEFAULT kigi host —
|
||||
/// the primary `/goal` host — the `general-purpose` toolset's only
|
||||
/// file-mutator is `search_replace` (`ToolKind::Edit`); the `write`
|
||||
/// tool (`ToolKind::Write`) is injection-only and absent from the
|
||||
@@ -2465,7 +2465,7 @@ fn subagent_keeps_default_flavor_when_parent_model_is_non_strict() {
|
||||
let mut ctx = ctx_with_toggle(HashMap::new());
|
||||
ctx.parent_agent_name = Some("ai-oncall-bot".to_string());
|
||||
ctx.parent_model_agent_type = Some(
|
||||
BuiltinAgentName::GrokBuildPlan.as_ref().to_string(),
|
||||
BuiltinAgentName::KigiPlan.as_ref().to_string(),
|
||||
);
|
||||
let mut def = resolve_agent_definition("general-purpose", &ctx).expect("resolves");
|
||||
resolve_subagent_toolset("general-purpose", None, &ctx, &mut def);
|
||||
@@ -2696,7 +2696,7 @@ async fn background_unknown_type_emits_subagent_finished_notification() {
|
||||
while let Ok(msg) = gateway_rx.try_recv() {
|
||||
if let kigi_acp_lib::AcpClientMessage::ExtNotification(args) = msg {
|
||||
let req: &acp::ExtNotification = &args.request;
|
||||
assert_eq!(req.method.as_ref(), "x.ai/session_notification");
|
||||
assert_eq!(req.method.as_ref(), "kigi/session_notification");
|
||||
let body = req.params.get();
|
||||
assert!(body.contains("subagent_finished"));
|
||||
assert!(body.contains(& subagent_id));
|
||||
@@ -3090,8 +3090,8 @@ fn subagent_auth_type_rule() {
|
||||
use kigi_chat_state::AuthType;
|
||||
let session = acp::AuthMethodId::new(CACHED_TOKEN_AUTH_METHOD_ID);
|
||||
let api_key = acp::AuthMethodId::new(XAI_API_KEY_METHOD_ID);
|
||||
let byok = byok_model_entry("grok-byok");
|
||||
let plain = test_model_entry("grok-plain");
|
||||
let byok = byok_model_entry("kigi-byok");
|
||||
let plain = test_model_entry("kigi-plain");
|
||||
assert_eq!(super::subagent_auth_type(Some(& byok), & session), AuthType::ApiKey);
|
||||
assert_eq!(super::subagent_auth_type(Some(& byok), & api_key), AuthType::ApiKey);
|
||||
assert_eq!(
|
||||
@@ -3104,14 +3104,14 @@ fn subagent_auth_type_rule() {
|
||||
#[test]
|
||||
fn fresh_tool_model_accepts_visible_key_and_internal_id() {
|
||||
let mut models = indexmap::IndexMap::new();
|
||||
models.insert("grok-3".to_string(), test_model_entry("grok-3-2025-02-15"));
|
||||
models.insert("kigi-3".to_string(), test_model_entry("kigi-3-2025-02-15"));
|
||||
assert!(
|
||||
super::handle_request::task_model_override_error(Some("grok-3"),
|
||||
super::handle_request::task_model_override_error(Some("kigi-3"),
|
||||
ModelOverrideProvenance::Tool, false, & models, false,).is_none(),
|
||||
"key lookup should succeed"
|
||||
);
|
||||
assert!(
|
||||
super::handle_request::task_model_override_error(Some("grok-3-2025-02-15"),
|
||||
super::handle_request::task_model_override_error(Some("kigi-3-2025-02-15"),
|
||||
ModelOverrideProvenance::Tool, false, & models, false,).is_none(),
|
||||
"info().model lookup should succeed"
|
||||
);
|
||||
@@ -3182,7 +3182,7 @@ fn fresh_tool_model_rejects_unknown_and_nonavailable_entries() {
|
||||
format!("Unknown Task.model slug '{requested}'. Valid model slugs: alpha, zeta. \
|
||||
Omit `model` to inherit the parent model.")
|
||||
);
|
||||
assert!(! error.contains("grok models"));
|
||||
assert!(! error.contains("kigi models"));
|
||||
}
|
||||
assert!(
|
||||
super::handle_request::task_model_override_error(Some("oauth-only"),
|
||||
|
||||
@@ -343,7 +343,7 @@ fn resumable_source_returns_info_for_completed_subagent() {
|
||||
child_cwd: "/workspace".into(),
|
||||
worktree_path: Some(PathBuf::from("/tmp/worktree-1")),
|
||||
snapshot_ref: None,
|
||||
effective_model_id: "grok-3".into(),
|
||||
effective_model_id: "kigi-3".into(),
|
||||
block_waited: false,
|
||||
explicitly_killed: false,
|
||||
},
|
||||
@@ -479,16 +479,16 @@ fn snapshot_ref_field_in_meta_roundtrips() {
|
||||
persona: None,
|
||||
resumed_from: None,
|
||||
child_cwd: None,
|
||||
worktree_path: Some("/tmp/grok-wt/sa-snap".into()),
|
||||
snapshot_ref: Some("refs/grok/subagent-snapshots/sa-snap".into()),
|
||||
worktree_path: Some("/tmp/kigi-wt/sa-snap".into()),
|
||||
snapshot_ref: Some("refs/kigi/subagent-snapshots/sa-snap".into()),
|
||||
effective_model_id: None,
|
||||
};
|
||||
let json = serde_json::to_string(&meta).unwrap();
|
||||
assert!(json.contains("snapshot_ref"));
|
||||
assert!(json.contains("refs/grok/subagent-snapshots/sa-snap"));
|
||||
assert!(json.contains("refs/kigi/subagent-snapshots/sa-snap"));
|
||||
let parsed: SubagentMeta = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(
|
||||
parsed.snapshot_ref.as_deref(), Some("refs/grok/subagent-snapshots/sa-snap")
|
||||
parsed.snapshot_ref.as_deref(), Some("refs/kigi/subagent-snapshots/sa-snap")
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
@@ -528,7 +528,7 @@ fn snapshot_test_meta(id: &str) -> SubagentMeta {
|
||||
persona: None,
|
||||
resumed_from: None,
|
||||
child_cwd: None,
|
||||
worktree_path: Some("/tmp/grok-wt/subagent-x".into()),
|
||||
worktree_path: Some("/tmp/kigi-wt/subagent-x".into()),
|
||||
snapshot_ref: None,
|
||||
effective_model_id: None,
|
||||
}
|
||||
@@ -540,14 +540,14 @@ fn update_subagent_meta_snapshot_ref_persists_to_disk() {
|
||||
let dir = tempfile::TempDir::new().unwrap();
|
||||
assert!(write_subagent_meta(dir.path(), & snapshot_test_meta("sa-write")));
|
||||
assert!(
|
||||
update_subagent_meta_snapshot_ref(dir.path(), "refs/grok/subagents/sa-write",
|
||||
update_subagent_meta_snapshot_ref(dir.path(), "refs/kigi/subagents/sa-write",
|
||||
"completed"), "persisting the ref into an existing meta.json must report success"
|
||||
);
|
||||
let data = std::fs::read_to_string(dir.path().join("meta.json")).unwrap();
|
||||
let reread: SubagentMeta = serde_json::from_str(&data).unwrap();
|
||||
assert_eq!(reread.snapshot_ref.as_deref(), Some("refs/grok/subagents/sa-write"));
|
||||
assert_eq!(reread.snapshot_ref.as_deref(), Some("refs/kigi/subagents/sa-write"));
|
||||
assert_eq!(reread.status, "completed");
|
||||
assert_eq!(reread.worktree_path.as_deref(), Some("/tmp/grok-wt/subagent-x"));
|
||||
assert_eq!(reread.worktree_path.as_deref(), Some("/tmp/kigi-wt/subagent-x"));
|
||||
}
|
||||
/// Missing meta.json → the writer reports failure (it `warn!`s), so the
|
||||
/// completion path keeps the worktree instead of removing it ref-less.
|
||||
@@ -555,7 +555,7 @@ fn update_subagent_meta_snapshot_ref_persists_to_disk() {
|
||||
fn update_subagent_meta_snapshot_ref_reports_failure_when_meta_missing() {
|
||||
let dir = tempfile::TempDir::new().unwrap();
|
||||
assert!(
|
||||
! update_subagent_meta_snapshot_ref(dir.path(), "refs/grok/subagents/sa-missing",
|
||||
! update_subagent_meta_snapshot_ref(dir.path(), "refs/kigi/subagents/sa-missing",
|
||||
"completed")
|
||||
);
|
||||
}
|
||||
@@ -569,12 +569,12 @@ fn snapshot_ref_write_promotes_nonterminal_status_to_terminal() {
|
||||
meta.status = "running".into();
|
||||
assert!(write_subagent_meta(dir.path(), & meta));
|
||||
assert!(
|
||||
update_subagent_meta_snapshot_ref(dir.path(), "refs/grok/subagents/x",
|
||||
update_subagent_meta_snapshot_ref(dir.path(), "refs/kigi/subagents/x",
|
||||
"completed")
|
||||
);
|
||||
let data = std::fs::read_to_string(dir.path().join("meta.json")).unwrap();
|
||||
let reread: SubagentMeta = serde_json::from_str(&data).unwrap();
|
||||
assert_eq!(Some("refs/grok/subagents/x"), reread.snapshot_ref.as_deref());
|
||||
assert_eq!(Some("refs/kigi/subagents/x"), reread.snapshot_ref.as_deref());
|
||||
assert_eq!("completed", reread.status);
|
||||
}
|
||||
/// The coordinator setter stamps the snapshot ref onto the in-memory
|
||||
@@ -600,17 +600,17 @@ async fn set_completed_snapshot_ref_updates_in_memory_entry() {
|
||||
.unwrap();
|
||||
assert!(before.snapshot_ref.is_none());
|
||||
coordinator
|
||||
.set_completed_snapshot_ref("sa-mem", "refs/grok/subagents/sa-mem".into());
|
||||
.set_completed_snapshot_ref("sa-mem", "refs/kigi/subagents/sa-mem".into());
|
||||
let after = coordinator
|
||||
.resumable_source_for("sa-mem", "session-A", Path::new("/tmp"))
|
||||
.unwrap();
|
||||
assert_eq!(after.snapshot_ref.as_deref(), Some("refs/grok/subagents/sa-mem"));
|
||||
assert_eq!(after.snapshot_ref.as_deref(), Some("refs/kigi/subagents/sa-mem"));
|
||||
}
|
||||
/// Unknown id is a no-op (entry already TTL-evicted; meta.json still holds it).
|
||||
#[test]
|
||||
fn set_completed_snapshot_ref_unknown_id_is_noop() {
|
||||
let mut coordinator = SubagentCoordinator::new();
|
||||
coordinator.set_completed_snapshot_ref("ghost", "refs/grok/subagents/ghost".into());
|
||||
coordinator.set_completed_snapshot_ref("ghost", "refs/kigi/subagents/ghost".into());
|
||||
assert!(
|
||||
coordinator.resumable_source_for("ghost", "session-A", Path::new("/tmp"))
|
||||
.is_none()
|
||||
@@ -719,7 +719,7 @@ async fn completion_snapshot_sequence_persists_ref_then_removes_worktree() {
|
||||
let meta_dir = temp.path().join("meta");
|
||||
write_subagent_meta(&meta_dir, &snapshot_test_meta("glue-1"));
|
||||
let mut coordinator = coordinator_with_completed("glue-1");
|
||||
let ref_name = "refs/grok/subagents/glue-1";
|
||||
let ref_name = "refs/kigi/subagents/glue-1";
|
||||
let snapshot_ref = crate::session::worktree::snapshot_subagent_worktree(
|
||||
&wt,
|
||||
&repo,
|
||||
@@ -744,7 +744,7 @@ async fn completion_snapshot_sequence_persists_ref_then_removes_worktree() {
|
||||
/// the tracker-retained direct `worktree_path` plus the snapshot_ref.
|
||||
#[tokio::test]
|
||||
async fn gate_on_completion_clears_model_facing_worktree_path_but_resume_retains_it() {
|
||||
let wt = PathBuf::from("/tmp/grok-wt/subagent-disp-1");
|
||||
let wt = PathBuf::from("/tmp/kigi-wt/subagent-disp-1");
|
||||
let mut coordinator = SubagentCoordinator::new();
|
||||
let mut tracker = dummy_tracker("disp-1", "session-A", "explore", "task");
|
||||
tracker.worktree_path = Some(wt.clone());
|
||||
@@ -762,21 +762,21 @@ async fn gate_on_completion_clears_model_facing_worktree_path_but_resume_retains
|
||||
}
|
||||
coordinator.move_to_completed("disp-1", "task".into(), "explore".into(), result);
|
||||
coordinator
|
||||
.set_completed_snapshot_ref("disp-1", "refs/grok/subagents/disp-1".into());
|
||||
.set_completed_snapshot_ref("disp-1", "refs/kigi/subagents/disp-1".into());
|
||||
let listed = coordinator.completed.get("disp-1").expect("completed entry");
|
||||
assert_eq!(None, listed.result.worktree_path);
|
||||
let src = coordinator
|
||||
.resumable_source_for("disp-1", "session-A", Path::new("/tmp"))
|
||||
.unwrap();
|
||||
assert_eq!(Some(wt), src.worktree_path);
|
||||
assert_eq!(Some("refs/grok/subagents/disp-1"), src.snapshot_ref.as_deref());
|
||||
assert_eq!(Some("refs/kigi/subagents/disp-1"), src.snapshot_ref.as_deref());
|
||||
}
|
||||
/// Gate on but the worktree was NOT removed (snapshot/persist/remove failed):
|
||||
/// the model-facing `result.worktree_path` is RETAINED so the parent can still
|
||||
/// locate the preserved dir.
|
||||
#[tokio::test]
|
||||
async fn gate_on_completion_retains_worktree_path_when_not_removed() {
|
||||
let wt = PathBuf::from("/tmp/grok-wt/subagent-keep-1");
|
||||
let wt = PathBuf::from("/tmp/kigi-wt/subagent-keep-1");
|
||||
let mut coordinator = SubagentCoordinator::new();
|
||||
coordinator.insert(dummy_tracker("keep-1", "session-A", "explore", "task"));
|
||||
let mut result = SubagentResult {
|
||||
@@ -817,7 +817,7 @@ async fn disposal_completes_before_subagent_is_observable() {
|
||||
write_subagent_meta(&meta_dir, &snapshot_test_meta("order-1"));
|
||||
let mut coordinator = SubagentCoordinator::new();
|
||||
coordinator.insert(dummy_tracker("order-1", "session-A", "explore", "task"));
|
||||
let ref_name = "refs/grok/subagents/order-1";
|
||||
let ref_name = "refs/kigi/subagents/order-1";
|
||||
let snapshot_ref = crate::session::worktree::snapshot_subagent_worktree(
|
||||
&wt,
|
||||
&repo,
|
||||
@@ -989,7 +989,7 @@ fn resume_inherited_cwd_requires_existing_non_worktree_dir() {
|
||||
};
|
||||
assert_eq!(resume_inherited_cwd(Some(& present)), Some(existing.as_str()));
|
||||
let missing = ResumeSourceData {
|
||||
child_cwd: "/no/such/dir/grok-missing".into(),
|
||||
child_cwd: "/no/such/dir/kigi-missing".into(),
|
||||
..present.clone()
|
||||
};
|
||||
assert_eq!(resume_inherited_cwd(Some(& missing)), None);
|
||||
@@ -1120,7 +1120,7 @@ fn token_estimation_accounts_for_images() {
|
||||
#[test]
|
||||
fn durable_fallback_roundtrips_child_cwd_and_worktree() {
|
||||
let dir = std::env::temp_dir()
|
||||
.join("grok-test-durable-resume")
|
||||
.join("kigi-test-durable-resume")
|
||||
.join(uuid::Uuid::now_v7().to_string());
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
let meta = SubagentMeta {
|
||||
@@ -1143,22 +1143,22 @@ fn durable_fallback_roundtrips_child_cwd_and_worktree() {
|
||||
persona: Some("implementer".into()),
|
||||
resumed_from: None,
|
||||
child_cwd: Some("/workspace/project".into()),
|
||||
worktree_path: Some("/tmp/grok-wt/sa-dur".into()),
|
||||
worktree_path: Some("/tmp/kigi-wt/sa-dur".into()),
|
||||
snapshot_ref: None,
|
||||
effective_model_id: Some("grok-3".into()),
|
||||
effective_model_id: Some("kigi-3".into()),
|
||||
};
|
||||
write_subagent_meta(&dir, &meta);
|
||||
let data = std::fs::read_to_string(dir.join("meta.json")).unwrap();
|
||||
let loaded: SubagentMeta = serde_json::from_str(&data).unwrap();
|
||||
assert_eq!(loaded.child_cwd.as_deref(), Some("/workspace/project"));
|
||||
assert_eq!(loaded.worktree_path.as_deref(), Some("/tmp/grok-wt/sa-dur"));
|
||||
assert_eq!(loaded.worktree_path.as_deref(), Some("/tmp/kigi-wt/sa-dur"));
|
||||
assert_eq!(loaded.status, "completed");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
#[test]
|
||||
fn durable_fallback_rejects_running_status() {
|
||||
let dir = std::env::temp_dir()
|
||||
.join("grok-test-durable-status")
|
||||
.join("kigi-test-durable-status")
|
||||
.join(uuid::Uuid::now_v7().to_string());
|
||||
let parent_dir = dir.join("subagents").join("sa-running");
|
||||
let _ = std::fs::create_dir_all(&parent_dir);
|
||||
@@ -1227,7 +1227,7 @@ fn drain_cancelled_finish_broadcasts(
|
||||
let kigi_acp_lib::AcpClientMessage::ExtNotification(args) = msg else {
|
||||
continue;
|
||||
};
|
||||
assert_eq!(args.request.method.as_ref(), "x.ai/session_notification");
|
||||
assert_eq!(args.request.method.as_ref(), "kigi/session_notification");
|
||||
let notification: SessionNotification = serde_json::from_str(
|
||||
args.request.params.get(),
|
||||
)
|
||||
@@ -1666,11 +1666,11 @@ fn resume_allows_matching_identity() {
|
||||
snapshot_ref: None,
|
||||
subagent_type: "general-purpose".into(),
|
||||
persona: Some("implementer".into()),
|
||||
model_id: Some("grok-3".into()),
|
||||
model_id: Some("kigi-3".into()),
|
||||
};
|
||||
assert_eq!("general-purpose", source.subagent_type);
|
||||
assert_eq!(Some("implementer"), source.persona.as_deref());
|
||||
assert_eq!(Some("grok-3"), source.model_id.as_deref());
|
||||
assert_eq!(Some("kigi-3"), source.model_id.as_deref());
|
||||
}
|
||||
#[test]
|
||||
fn resume_identity_does_not_gate_on_model() {
|
||||
@@ -1682,21 +1682,21 @@ fn resume_identity_does_not_gate_on_model() {
|
||||
snapshot_ref: None,
|
||||
subagent_type: "general-purpose".into(),
|
||||
persona: None,
|
||||
model_id: Some("grok-3".into()),
|
||||
model_id: Some("kigi-3".into()),
|
||||
};
|
||||
assert!(
|
||||
kigi_subagent_resolution::validate_resume_identity("general-purpose", None, &
|
||||
source,).is_ok()
|
||||
);
|
||||
assert_eq!(
|
||||
source.model_id.as_deref(), Some("grok-3"),
|
||||
source.model_id.as_deref(), Some("kigi-3"),
|
||||
"source model remains available for pinning"
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn durable_meta_roundtrips_effective_model_id() {
|
||||
let dir = std::env::temp_dir()
|
||||
.join("grok-test-model-roundtrip")
|
||||
.join("kigi-test-model-roundtrip")
|
||||
.join(uuid::Uuid::now_v7().to_string());
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
let meta = SubagentMeta {
|
||||
@@ -1721,24 +1721,24 @@ fn durable_meta_roundtrips_effective_model_id() {
|
||||
child_cwd: Some("/workspace".into()),
|
||||
worktree_path: None,
|
||||
snapshot_ref: None,
|
||||
effective_model_id: Some("grok-3".into()),
|
||||
effective_model_id: Some("kigi-3".into()),
|
||||
};
|
||||
write_subagent_meta(&dir, &meta);
|
||||
let data = std::fs::read_to_string(dir.join("meta.json")).unwrap();
|
||||
let loaded: SubagentMeta = serde_json::from_str(&data).unwrap();
|
||||
assert_eq!(
|
||||
loaded.effective_model_id.as_deref(), Some("grok-3"),
|
||||
loaded.effective_model_id.as_deref(), Some("kigi-3"),
|
||||
"model ID should round-trip through meta.json"
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
#[test]
|
||||
fn resume_model_pinning_overrides_default_resolution() {
|
||||
let source_model = Some("grok-3".to_string());
|
||||
let resolved_model = "grok-light";
|
||||
let source_model = Some("kigi-3".to_string());
|
||||
let resolved_model = "kigi-light";
|
||||
let needs_pin = source_model.as_deref() != Some(resolved_model);
|
||||
assert!(needs_pin, "resolved model differs from source — pinning should trigger");
|
||||
let resolved_same = "grok-3";
|
||||
let resolved_same = "kigi-3";
|
||||
let no_pin = source_model.as_deref() == Some(resolved_same);
|
||||
assert!(no_pin, "same model — no pinning needed");
|
||||
}
|
||||
@@ -2108,19 +2108,19 @@ fn ctx_with_parent_chat_state(
|
||||
#[tokio::test]
|
||||
async fn read_parent_sampling_config_keeps_auto_catalog_id_with_routing_slug() {
|
||||
let mut models = indexmap::IndexMap::new();
|
||||
models.insert("auto".to_string(), test_model_entry("grok-4.5"));
|
||||
let ctx = ctx_with_parent_chat_state("auto", "grok-4.5", "composer-2-fast", models);
|
||||
models.insert("auto".to_string(), test_model_entry("kigi-4.5"));
|
||||
let ctx = ctx_with_parent_chat_state("auto", "kigi-4.5", "composer-2-fast", models);
|
||||
let (config, model_id) = read_parent_sampling_config(&ctx).await;
|
||||
assert_eq!(config.model, "grok-4.5");
|
||||
assert_eq!(config.model, "kigi-4.5");
|
||||
assert_eq!(model_id.0.as_ref(), "auto");
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn read_parent_sampling_config_keeps_auto_when_catalog_has_slug_key_only() {
|
||||
let mut models = indexmap::IndexMap::new();
|
||||
models.insert("grok-4.5".to_string(), test_model_entry("grok-4.5"));
|
||||
let ctx = ctx_with_parent_chat_state("auto", "grok-4.5", "auto", models);
|
||||
models.insert("kigi-4.5".to_string(), test_model_entry("kigi-4.5"));
|
||||
let ctx = ctx_with_parent_chat_state("auto", "kigi-4.5", "auto", models);
|
||||
let (config, model_id) = read_parent_sampling_config(&ctx).await;
|
||||
assert_eq!(config.model, "grok-4.5");
|
||||
assert_eq!(config.model, "kigi-4.5");
|
||||
assert_eq!(model_id.0.as_ref(), "auto");
|
||||
}
|
||||
#[tokio::test]
|
||||
@@ -2161,11 +2161,11 @@ async fn read_parent_sampling_config_ignores_global_default() {
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn read_parent_sampling_config_resolves_backend_search_from_catalog() {
|
||||
let mut entry = test_model_entry("grok-4.5");
|
||||
let mut entry = test_model_entry("kigi-4.5");
|
||||
entry.info.supports_backend_search = true;
|
||||
let mut models = indexmap::IndexMap::new();
|
||||
models.insert("auto".to_string(), entry);
|
||||
let mut ctx = ctx_with_parent_chat_state("auto", "grok-4.5", "auto", models);
|
||||
let mut ctx = ctx_with_parent_chat_state("auto", "kigi-4.5", "auto", models);
|
||||
ctx.sampling_config.supports_backend_search = false;
|
||||
let (config, _model_id) = read_parent_sampling_config(&ctx).await;
|
||||
assert!(
|
||||
@@ -2201,11 +2201,11 @@ async fn read_parent_sampling_config_fallback_resolves_backend_search_from_catal
|
||||
#[tokio::test]
|
||||
async fn read_parent_sampling_config_resolves_compactions_remaining_from_catalog() {
|
||||
use kigi_sampling_types::CompactionsRemaining;
|
||||
let mut entry = test_model_entry("grok-4.5");
|
||||
let mut entry = test_model_entry("kigi-4.5");
|
||||
entry.info.compactions_remaining = Some(CompactionsRemaining::Dynamic(true));
|
||||
let mut models = indexmap::IndexMap::new();
|
||||
models.insert("auto".to_string(), entry);
|
||||
let mut ctx = ctx_with_parent_chat_state("auto", "grok-4.5", "auto", models);
|
||||
let mut ctx = ctx_with_parent_chat_state("auto", "kigi-4.5", "auto", models);
|
||||
ctx.sampling_config.compactions_remaining = None;
|
||||
let (config, _model_id) = read_parent_sampling_config(&ctx).await;
|
||||
assert_eq!(
|
||||
@@ -2360,7 +2360,7 @@ async fn fork_context_pins_parent_model_over_overrides() {
|
||||
#[tokio::test]
|
||||
async fn resolve_subagent_inherits_parent_model_without_pins() {
|
||||
use kigi_agent::config::ModelOverride;
|
||||
for parent_model in ["grok-4.5", "composer-2-fast", "my-custom-byok-model"] {
|
||||
for parent_model in ["kigi-4.5", "composer-2-fast", "my-custom-byok-model"] {
|
||||
let mut ctx = ctx_with_toggle(HashMap::new());
|
||||
ctx.sampling_config.model = parent_model.to_string();
|
||||
ctx.model_id = acp::ModelId::new(parent_model);
|
||||
@@ -2379,12 +2379,12 @@ async fn resolve_subagent_inherits_parent_model_without_pins() {
|
||||
}
|
||||
/// An explicit `[subagents.models]` pin routes the subagent to that
|
||||
/// model regardless of the parent model — both a light parent
|
||||
/// (`grok-4.5`) and a custom parent (`composer-2-fast`)
|
||||
/// (`kigi-4.5`) and a custom parent (`composer-2-fast`)
|
||||
/// honor the pin identically now that the heavy-model gate is gone.
|
||||
#[tokio::test]
|
||||
async fn resolve_subagent_config_override_pin_applies_for_any_parent() {
|
||||
use kigi_agent::config::ModelOverride;
|
||||
for parent_model in ["grok-4.5", "composer-2-fast"] {
|
||||
for parent_model in ["kigi-4.5", "composer-2-fast"] {
|
||||
let mut ctx = ctx_with_toggle(HashMap::new());
|
||||
ctx.sampling_config.model = parent_model.to_string();
|
||||
ctx.model_id = acp::ModelId::new(parent_model);
|
||||
@@ -2411,8 +2411,8 @@ async fn resolve_subagent_config_override_pin_applies_for_any_parent() {
|
||||
async fn resolve_subagent_agent_definition_pin_applies_for_light_parent() {
|
||||
use kigi_agent::config::ModelOverride;
|
||||
let mut ctx = ctx_with_toggle(HashMap::new());
|
||||
ctx.sampling_config.model = "grok-4.5".to_string();
|
||||
ctx.model_id = acp::ModelId::new("grok-4.5");
|
||||
ctx.sampling_config.model = "kigi-4.5".to_string();
|
||||
ctx.model_id = acp::ModelId::new("kigi-4.5");
|
||||
ctx.available_models
|
||||
.insert("pinned-model".to_string(), test_model_entry("pinned-model"));
|
||||
let agent_model = ModelOverride::Override("pinned-model".to_string());
|
||||
@@ -2431,8 +2431,8 @@ async fn resolve_subagent_agent_definition_pin_applies_for_light_parent() {
|
||||
async fn resolve_subagent_config_override_wins_over_agent_definition() {
|
||||
use kigi_agent::config::ModelOverride;
|
||||
let mut ctx = ctx_with_toggle(HashMap::new());
|
||||
ctx.sampling_config.model = "grok-4.5".to_string();
|
||||
ctx.model_id = acp::ModelId::new("grok-4.5");
|
||||
ctx.sampling_config.model = "kigi-4.5".to_string();
|
||||
ctx.model_id = acp::ModelId::new("kigi-4.5");
|
||||
ctx.available_models
|
||||
.insert("config-pin".to_string(), test_model_entry("config-pin"));
|
||||
ctx.available_models
|
||||
@@ -2454,8 +2454,8 @@ async fn resolve_subagent_config_override_wins_over_agent_definition() {
|
||||
async fn resolve_subagent_config_override_unknown_model_falls_through_to_inherit() {
|
||||
use kigi_agent::config::ModelOverride;
|
||||
let mut ctx = ctx_with_toggle(HashMap::new());
|
||||
ctx.sampling_config.model = "grok-4.5".to_string();
|
||||
ctx.model_id = acp::ModelId::new("grok-4.5");
|
||||
ctx.sampling_config.model = "kigi-4.5".to_string();
|
||||
ctx.model_id = acp::ModelId::new("kigi-4.5");
|
||||
ctx.subagent_model_overrides
|
||||
.insert("explore".to_string(), "does-not-exist".to_string());
|
||||
let (config, model_id) = resolve_subagent_sampling_config(
|
||||
@@ -2464,8 +2464,8 @@ async fn resolve_subagent_config_override_unknown_model_falls_through_to_inherit
|
||||
&ctx,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(config.model, "grok-4.5");
|
||||
assert_eq!(model_id.0.as_ref(), "grok-4.5");
|
||||
assert_eq!(config.model, "kigi-4.5");
|
||||
assert_eq!(model_id.0.as_ref(), "kigi-4.5");
|
||||
}
|
||||
/// An unresolvable `AgentDefinition.model` pin (model absent from
|
||||
/// `available_models`) falls through to inherit the parent model.
|
||||
@@ -2473,8 +2473,8 @@ async fn resolve_subagent_config_override_unknown_model_falls_through_to_inherit
|
||||
async fn resolve_subagent_agent_definition_unknown_model_falls_through_to_inherit() {
|
||||
use kigi_agent::config::ModelOverride;
|
||||
let mut ctx = ctx_with_toggle(HashMap::new());
|
||||
ctx.sampling_config.model = "grok-4.5".to_string();
|
||||
ctx.model_id = acp::ModelId::new("grok-4.5");
|
||||
ctx.sampling_config.model = "kigi-4.5".to_string();
|
||||
ctx.model_id = acp::ModelId::new("kigi-4.5");
|
||||
let agent_model = ModelOverride::Override("does-not-exist".to_string());
|
||||
let (config, model_id) = resolve_subagent_sampling_config(
|
||||
"explore",
|
||||
@@ -2482,8 +2482,8 @@ async fn resolve_subagent_agent_definition_unknown_model_falls_through_to_inheri
|
||||
&ctx,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(config.model, "grok-4.5");
|
||||
assert_eq!(model_id.0.as_ref(), "grok-4.5");
|
||||
assert_eq!(config.model, "kigi-4.5");
|
||||
assert_eq!(model_id.0.as_ref(), "kigi-4.5");
|
||||
}
|
||||
#[test]
|
||||
fn key_prefix_truncates_to_8_chars() {
|
||||
|
||||
Reference in New Issue
Block a user