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.
107 lines
4.3 KiB
Rust
107 lines
4.3 KiB
Rust
//! Credential dependency-inversion seam for outbound HTTP made by the
|
|
//! data-collector. Shell installs `ShellAuthCredentialProvider` wrapping
|
|
//! `AuthManager` + `TokenRefresher`; data-collector code holds an
|
|
//! `Arc<dyn AuthCredentialProvider>`.
|
|
|
|
use reqwest::RequestBuilder;
|
|
|
|
use crate::visibility::HttpAuth;
|
|
|
|
/// Snapshot of the currently effective credentials. Used by callers
|
|
/// that build their own header maps (the OTel OTLP exporter) or that
|
|
/// need the bearer prefix for 401-attribution telemetry.
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct CredentialSnapshot {
|
|
/// Bearer token. `None` when no auth is configured (CI / `--api-key` headless).
|
|
pub token: Option<String>,
|
|
/// User identifier matching the bearer token's owner. `None` when no auth
|
|
/// is configured or when the underlying provider has no concept of user
|
|
/// identity (`StaticAuthCredentialProvider`). Read by the OTel layer to
|
|
/// populate the `user.id` resource attribute.
|
|
pub user_id: Option<String>,
|
|
/// `uuidv5(NAMESPACE_OID, deployment_key)`, set only for deployment-key auth.
|
|
pub deployment_id: Option<String>,
|
|
/// `uuidv5(NAMESPACE_OID, api_key)`, set only for `AuthMode::ApiKey`.
|
|
pub api_key_id: Option<String>,
|
|
}
|
|
|
|
/// Source of truth for outbound auth on data-collector requests.
|
|
///
|
|
/// Supertrait of `HttpAuth` so a single impl satisfies both this trait
|
|
/// (refresh-aware snapshot + 401 recovery) and the visibility seam
|
|
/// (header construction). Callers add headers via `HttpAuth::apply`.
|
|
#[async_trait::async_trait]
|
|
pub trait AuthCredentialProvider: HttpAuth + Send + Sync + 'static {
|
|
/// Return the current credential snapshot. Implementations should
|
|
/// issue a cheap disk re-read (`AuthManager::refresh`) before
|
|
/// snapshotting so callers see updates from sibling processes
|
|
/// (`kigi-desktop`, `kigi login`). The `token` field MUST mirror
|
|
/// the bearer that `HttpAuth::apply` would send on the wire so
|
|
/// 401-attribution prefixes match the actual request.
|
|
fn snapshot(&self) -> CredentialSnapshot;
|
|
|
|
/// Attempt to obtain a fresh token. Returns `true` if a different
|
|
/// token was obtained -- caller should retry the failed request once.
|
|
/// Returns `false` if no refresher is configured or refresh failed.
|
|
async fn refresh_after_unauthorized(&self) -> bool;
|
|
|
|
/// Whether the provider holds a credential worth a real outbound attempt —
|
|
/// an unexpired token (in memory or on disk), or a static key. Default
|
|
/// `true` always attempts.
|
|
fn has_usable_credential(&self) -> bool {
|
|
true
|
|
}
|
|
}
|
|
|
|
/// Static credential provider. Used by tests and by callers that pass a
|
|
/// raw `&str` token with no `AuthManager` available.
|
|
///
|
|
/// `apply()` delegates to the underlying `HttpAuth::apply()`.
|
|
/// `refresh_after_unauthorized()` always returns `false`.
|
|
///
|
|
/// `bearer` is the wire bearer the inner `HttpAuth` will send in the
|
|
/// `Authorization` header. Stored alongside the inner so `snapshot().token`
|
|
/// returns the same prefix that goes out on the wire (used by
|
|
/// 401-attribution telemetry). `None` when no bearer is configured.
|
|
pub struct StaticAuthCredentialProvider {
|
|
inner: Box<dyn HttpAuth>,
|
|
bearer: Option<String>,
|
|
}
|
|
|
|
impl StaticAuthCredentialProvider {
|
|
/// Wrap `inner` so callers see it as an `AuthCredentialProvider`. Pass
|
|
/// the bearer token that `inner.apply()` will send in the `Authorization`
|
|
/// header so `snapshot().token` reflects the wire bearer truthfully.
|
|
pub fn new(inner: Box<dyn HttpAuth>, bearer: Option<String>) -> Self {
|
|
Self { inner, bearer }
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for StaticAuthCredentialProvider {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("StaticAuthCredentialProvider")
|
|
.field("has_bearer", &self.bearer.is_some())
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl HttpAuth for StaticAuthCredentialProvider {
|
|
fn apply(&self, builder: RequestBuilder, base_url: &str) -> RequestBuilder {
|
|
self.inner.apply(builder, base_url)
|
|
}
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl AuthCredentialProvider for StaticAuthCredentialProvider {
|
|
fn snapshot(&self) -> CredentialSnapshot {
|
|
CredentialSnapshot {
|
|
token: self.bearer.clone(),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
async fn refresh_after_unauthorized(&self) -> bool {
|
|
false
|
|
}
|
|
}
|