§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:
1 parent
86e3724310
commit
6f31415ed6
1056 files changed
+8410
-18307
No files matched your search
@@ -0,0 +1,235 @@
|
||||
//! `web_fetch` tool — client-side URL fetching with improved HTML-to-markdown
|
||||
//! conversion and SSRF protection.
|
||||
//!
|
||||
//! Fetches a URL via `reqwest`, converts HTML to markdown via `htmd` (with
|
||||
//! `<script>`/`<style>`/etc. stripped), and returns content to the model.
|
||||
//! Prefers `text/markdown` in the Accept header so doc sites that serve markdown
|
||||
//! directly bypass the HTML conversion entirely.
|
||||
|
||||
mod artifact;
|
||||
mod cache;
|
||||
pub mod client;
|
||||
pub mod config;
|
||||
pub mod domain;
|
||||
pub mod error;
|
||||
mod http;
|
||||
pub(crate) mod overflow;
|
||||
mod ssrf;
|
||||
|
||||
pub use client::WebFetchClient;
|
||||
pub use config::WebFetchParams;
|
||||
pub use domain::{DomainMatcher, domain_from_url};
|
||||
pub use error::WebFetchError;
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
// Config enum (feature flag gating)
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Configuration for the `web_fetch` tool.
|
||||
///
|
||||
/// When `Enabled`, the tool is registered and a `WebFetchClient` is injected
|
||||
/// into `Resources`. When `Disabled` (default), the tool is not registered.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub enum WebFetchConfig {
|
||||
#[default]
|
||||
Disabled,
|
||||
Enabled {
|
||||
/// Runtime parameters (allowed_domains, proxy_endpoint, timeouts, etc.)
|
||||
params: WebFetchParams,
|
||||
},
|
||||
}
|
||||
|
||||
impl WebFetchConfig {
|
||||
/// Returns `true` when the config is the `Enabled` variant.
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
matches!(self, Self::Enabled { .. })
|
||||
}
|
||||
}
|
||||
|
||||
use crate::types::output::WebFetchOutput;
|
||||
use crate::types::requirements::{Expr, ToolRequirement};
|
||||
use crate::types::resources::SessionFolder;
|
||||
use crate::types::tool::{ToolKind, ToolNamespace};
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
// Input
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
|
||||
pub struct WebFetchInput {
|
||||
/// The URL to fetch content from.
|
||||
#[schemars(description = "The URL to fetch content from.")]
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
// Tool
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct WebFetchTool;
|
||||
|
||||
impl crate::types::tool_metadata::ToolMetadata for WebFetchTool {
|
||||
fn kind(&self) -> ToolKind {
|
||||
ToolKind::WebFetch
|
||||
}
|
||||
|
||||
fn tool_namespace(&self) -> ToolNamespace {
|
||||
ToolNamespace::Kigi
|
||||
}
|
||||
|
||||
fn description_template(&self) -> &str {
|
||||
r#"Fetch the content of a specific URL and return it as markdown.
|
||||
|
||||
IMPORTANT: ${{ tools.by_kind.web_fetch }} WILL FAIL for authenticated or private URLs (e.g. Google Docs, Confluence, Jira, GitHub private repos). Use specialized MCP tools for those instead.
|
||||
|
||||
Usage notes:
|
||||
- HTTP URLs will be automatically upgraded to HTTPS
|
||||
- Long pages will be truncated to fit your context window"#
|
||||
}
|
||||
|
||||
fn versioned_definition(
|
||||
&self,
|
||||
_contract_version: Option<&str>,
|
||||
client_name: &str,
|
||||
description_override: Option<&str>,
|
||||
renderer: &crate::types::template_renderer::TemplateRenderer,
|
||||
param_map: &std::collections::HashMap<String, String>,
|
||||
input_schema: &serde_json::Value,
|
||||
effective_params: &serde_json::Value,
|
||||
) -> crate::types::definition::ToolDefinition {
|
||||
let params: WebFetchParams = serde_json::from_value(effective_params.clone())
|
||||
.unwrap_or_else(|e| {
|
||||
tracing::warn!("Failed to deserialize WebFetchParams: {e}");
|
||||
WebFetchParams::default()
|
||||
});
|
||||
let raw_desc = description_override.unwrap_or_else(|| {
|
||||
crate::types::tool_metadata::ToolMetadata::description_template(self)
|
||||
});
|
||||
let extras = serde_json::json!({
|
||||
"proxy_enabled": params.proxy_endpoint.is_some(),
|
||||
});
|
||||
let description = renderer
|
||||
.render_with_extra(raw_desc, &extras)
|
||||
.unwrap_or_else(|e| {
|
||||
tracing::warn!("Description template render failed, using raw: {e}");
|
||||
raw_desc.to_string()
|
||||
});
|
||||
let remapped_schema = if param_map.is_empty() {
|
||||
input_schema.clone()
|
||||
} else {
|
||||
crate::util::remap::remap_schema_properties(input_schema, param_map)
|
||||
};
|
||||
crate::types::definition::ToolDefinition::function(
|
||||
client_name,
|
||||
Some(&description),
|
||||
remapped_schema,
|
||||
)
|
||||
}
|
||||
|
||||
fn requires_expr(&self) -> Expr<ToolRequirement> {
|
||||
Expr::True
|
||||
}
|
||||
}
|
||||
|
||||
impl kigi_tool_runtime::Tool for WebFetchTool {
|
||||
type Args = WebFetchInput;
|
||||
type Output = WebFetchOutput;
|
||||
|
||||
fn id(&self) -> kigi_tool_protocol::ToolId {
|
||||
kigi_tool_protocol::ToolId::new("web_fetch").expect("valid tool id")
|
||||
}
|
||||
|
||||
fn description(
|
||||
&self,
|
||||
_ctx: &::kigi_tool_runtime::ListToolsContext,
|
||||
) -> kigi_tool_types::ToolDescription {
|
||||
kigi_tool_types::ToolDescription::new(
|
||||
"web_fetch",
|
||||
crate::types::tool_metadata::ToolMetadata::description_template(self),
|
||||
)
|
||||
}
|
||||
|
||||
fn capabilities(&self) -> kigi_tool_protocol::ToolCapabilities {
|
||||
kigi_tool_protocol::ToolCapabilities {
|
||||
is_read_only: true,
|
||||
tool_scope: Some(kigi_tool_protocol::ToolScope::Read),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "tool.web_fetch", skip_all, fields(url = %input.url))]
|
||||
async fn run(
|
||||
&self,
|
||||
ctx: kigi_tool_runtime::ToolCallContext,
|
||||
input: WebFetchInput,
|
||||
) -> Result<WebFetchOutput, kigi_tool_runtime::ToolError> {
|
||||
use crate::types::tool_metadata::shared_resources;
|
||||
let resources = shared_resources(&ctx)?;
|
||||
|
||||
let (client, session_folder, read_tool_name, execute_tool_name) = {
|
||||
let res = resources.lock().await;
|
||||
let client = res.require::<WebFetchClient>()?.clone();
|
||||
let session_folder = res.get::<SessionFolder>().map(|folder| folder.0.clone());
|
||||
let renderer = res.get::<crate::types::template_renderer::TemplateRenderer>();
|
||||
let read_tool_name = renderer
|
||||
.and_then(|renderer| renderer.tool_for_kind(ToolKind::Read))
|
||||
.map(str::to_owned);
|
||||
let execute_tool_name = renderer
|
||||
.and_then(|renderer| renderer.tool_for_kind(ToolKind::Execute))
|
||||
.map(str::to_owned);
|
||||
(client, session_folder, read_tool_name, execute_tool_name)
|
||||
};
|
||||
|
||||
let output = client
|
||||
.fetch(
|
||||
&input.url,
|
||||
ctx.call_id.as_str(),
|
||||
session_folder.as_deref(),
|
||||
read_tool_name.as_deref(),
|
||||
execute_tool_name.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::types::tool_metadata::test_ctx_with_call_id;
|
||||
|
||||
#[test]
|
||||
fn tool_name_and_description() {
|
||||
let tool = WebFetchTool;
|
||||
assert_eq!(kigi_tool_runtime::Tool::id(&tool).as_str(), "web_fetch");
|
||||
assert_eq!(
|
||||
crate::types::tool_metadata::ToolMetadata::kind(&tool),
|
||||
ToolKind::WebFetch
|
||||
);
|
||||
assert!(
|
||||
crate::types::tool_metadata::ToolMetadata::description_template(&tool)
|
||||
.contains("Fetch the content of a specific URL")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn errors_when_client_not_in_resources() {
|
||||
let resources = crate::types::resources::Resources::new();
|
||||
let tool = WebFetchTool;
|
||||
let result = kigi_tool_runtime::Tool::run(
|
||||
&tool,
|
||||
test_ctx_with_call_id(resources.into_shared(), "test-call"),
|
||||
WebFetchInput {
|
||||
url: "https://example.com".into(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
assert!(result.is_err());
|
||||
let err_msg = result.unwrap_err().to_string();
|
||||
assert!(
|
||||
err_msg.contains("missing required resource"),
|
||||
"Expected 'missing required resource' error, got: {err_msg}"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user