//! Context for building tool usage guides.
//!
//! This module provides language detection and context building for generating
//! localized tool usage guidelines that match the language of the system prompt.
/// Language options for guide generation.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GuideLanguage {
Chinese,
English,
}
impl GuideLanguage {
/// Detects the language from source text.
///
/// Returns `Chinese` if CJK characters are detected, otherwise `English`.
pub fn detect(source: &str) -> Self {
if source.chars().any(is_cjk) {
Self::Chinese
} else {
Self::English
}
}
}
/// Build context for generating tool guides.
///
/// Contains language settings and configuration options for guide generation.
#[derive(Debug, Clone)]
pub struct GuideBuildContext {
/// Detected or configured language for guide content
pub language: GuideLanguage,
/// Whether to include best practices section
pub include_best_practices: bool,
/// Maximum number of examples to include per tool
pub max_examples_per_tool: usize,
/// Discoverable tools that are currently activated for the session.
/// Activated discoverable tools render with full guides; inactive ones
/// render with short summaries only.
pub activated_discoverable_tools: std::collections::BTreeSet<String>,
}
impl Default for GuideBuildContext {
fn default() -> Self {
Self {
language: GuideLanguage::English,
include_best_practices: true,
max_examples_per_tool: 2,
activated_discoverable_tools: std::collections::BTreeSet::new(),
}
}
}
impl GuideBuildContext {
/// Creates a build context by detecting language from a system prompt.
///
/// # Arguments
///
/// * `prompt` - The system prompt to analyze for language detection
pub fn from_system_prompt(prompt: &str) -> Self {
Self {
language: GuideLanguage::detect(prompt),
..Self::default()
}
}
/// Returns best practices appropriate for the configured language.
pub fn best_practices(&self) -> &'static [&'static str] {
match self.language {
GuideLanguage::Chinese => &[
"Assist with defensive security tasks only; refuse offensive security requests, credential harvesting, and malware-oriented code changes.",
"For multi-step or non-trivial work, use Task to maintain a shared task list and keep statuses updated continuously.",
"Keep exactly one Task item in in_progress whenever possible; mark items completed immediately after finishing.",
"Read existing files before editing them. Use Edit for targeted replacements and Write only for full-file writes.",
"Use Glob for file discovery and Grep for content search. Do not use exec_command for find/grep/cat/head/tail/sed/awk/echo style file operations when dedicated tools exist.",
"For code search, narrow scope first: Glob -> Grep(files_with_matches) -> Read -> Grep(content/multiline).",
"Avoid broad Grep queries; keep head_limit small and add path/glob/type before multiline or content-heavy searches.",
"Use exec_command only for real terminal operations (build/test/git/npm/docker/etc.), not for file browsing or user-facing communication.",
"When multiple exec_command calls are independent, run them in parallel. For dependent commands, chain with &&.",
"When exec_command returns a running session_id, use write_stdin with empty chars to pull incremental output and final exit status. Choose yield_time_ms to bound each wait; completion does not send an automatic user notification. Unread completed results expire five minutes after completion, including during a diagnostic pause; handles do not survive Host restart. Poll required commands to a terminal result before finishing the task.",
"When polling reports sustained silence and repeated empty readouts, follow its advisory: do useful work or check one command-owned progress signal before polling again. Quiet output or an existing report does not prove completion; keep the handle, continue waiting when appropriate, and report a concrete blocker if progress cannot be established. A Human-controlled Root can pause polling after the reminder is repeatedly ignored, retaining the command handle for continuation. Do not stop/restart a command only because it is quiet.",
"**Parallel tool calls are strongly preferred.** The following read-only tools can safely run in parallel and SHOULD be called together in the same response whenever possible: FileExists, Glob, GetCurrentDir, GetFileInfo, Grep, Read, ViewImage, WebFetch, session_history_current, session_history. For example, if you need to read 3 files, emit all 3 Read calls in one response instead of one at a time.",
"For commit requests: inspect git status, git diff, and recent git log first; commit only when explicitly requested; avoid interactive git flags; use HEREDOC for multi-line commit messages.",
"For pull request requests: review all commits/diff since base branch, use gh to create PR with summary and test plan, and return the PR URL.",
"When referencing code locations in responses, use file_path:line_number format.",
"Use Plan to delegate planning to its read-only child and keep the root session in normal orchestration mode. Use ExitPlanMode only to leave an already-active legacy plan-mode session.",
],
GuideLanguage::English => &[
"Assist with defensive security tasks only; refuse offensive security requests, credential harvesting, and malware-oriented code changes.",
"For multi-step or non-trivial work, use Task to maintain a shared task list and keep statuses updated continuously.",
"Keep exactly one Task item in in_progress whenever possible; mark items completed immediately after finishing.",
"Read existing files before editing them. Use Edit for targeted replacements and Write only for full-file writes.",
"Use Glob for file discovery and Grep for content search. Do not use exec_command for find/grep/cat/head/tail/sed/awk/echo style file operations when dedicated tools exist.",
"For code search, narrow scope first: Glob -> Grep(files_with_matches) -> Read -> Grep(content/multiline).",
"Avoid broad Grep queries; keep head_limit small and add path/glob/type before multiline or content-heavy searches.",
"Use exec_command only for real terminal operations (build/test/git/npm/docker/etc.), not for file browsing or user-facing communication.",
"When multiple exec_command calls are independent, run them in parallel. For dependent commands, chain with &&.",
"When exec_command returns a running session_id, use write_stdin with empty chars to pull incremental output and final exit status. Choose yield_time_ms to bound each wait; completion does not send an automatic user notification. Unread completed results expire five minutes after completion, including during a diagnostic pause; handles do not survive Host restart. Poll required commands to a terminal result before finishing the task.",
"When polling reports sustained silence and repeated empty readouts, follow its advisory: do useful work or check one command-owned progress signal before polling again. Quiet output or an existing report does not prove completion; keep the handle, continue waiting when appropriate, and report a concrete blocker if progress cannot be established. A Human-controlled Root can pause polling after the reminder is repeatedly ignored, retaining the command handle for continuation. Do not stop/restart a command only because it is quiet.",
"**Parallel tool calls are strongly preferred.** The following read-only tools can safely run in parallel and SHOULD be called together in the same response whenever possible: FileExists, Glob, GetCurrentDir, GetFileInfo, Grep, Read, ViewImage, WebFetch, session_history_current, session_history. For example, if you need to read 3 files, emit all 3 Read calls in one response instead of one at a time.",
"For commit requests: inspect git status, git diff, and recent git log first; commit only when explicitly requested; avoid interactive git flags; use HEREDOC for multi-line commit messages.",
"For pull request requests: review all commits/diff since base branch, use gh to create PR with summary and test plan, and return the PR URL.",
"When referencing code locations in responses, use file_path:line_number format.",
"Use Plan to delegate planning to its read-only child and keep the root session in normal orchestration mode. Use ExitPlanMode only to leave an already-active legacy plan-mode session.",
],
}
}
}
fn is_cjk(ch: char) -> bool {
matches!(
ch,
'\u{3400}'..='\u{4DBF}' | '\u{4E00}'..='\u{9FFF}' | '\u{F900}'..='\u{FAFF}'
)
}
#[cfg(test)]
mod tests {
use super::{GuideBuildContext, GuideLanguage};
#[test]
fn detect_language_prefers_chinese_when_cjk_present() {
assert_eq!(
GuideLanguage::detect("Please help me modify this file"),
GuideLanguage::English
);
}
#[test]
fn best_practices_prefer_delegated_plan_without_legacy_mode_switch() {
for language in [GuideLanguage::Chinese, GuideLanguage::English] {
let context = GuideBuildContext {
language,
..GuideBuildContext::default()
};
let guidance = context.best_practices().join("\n");
assert!(guidance.contains("delegate planning to its read-only child"));
assert!(guidance.contains("already-active legacy plan-mode session"));
}
}
#[test]
fn best_practices_teach_model_owned_exec_polling() {
for language in [GuideLanguage::Chinese, GuideLanguage::English] {
let context = GuideBuildContext {
language,
..GuideBuildContext::default()
};
let guidance = context.best_practices().join("\n");
assert!(guidance.contains("exec_command"));
assert!(guidance.contains("write_stdin with empty chars"));
assert!(guidance.contains("does not send an automatic user notification"));
assert!(guidance.contains("follow its advisory"));
assert!(guidance.contains("does not prove completion"));
assert!(!guidance.contains("Bash"));
}
}
#[test]
fn detect_language_defaults_to_english_without_cjk() {
assert_eq!(
GuideLanguage::detect("Please inspect the codebase"),
GuideLanguage::English
);
}
#[test]
fn from_system_prompt_carries_detected_language() {
let context = GuideBuildContext::from_system_prompt("You are a coding assistant.");
assert_eq!(context.language, GuideLanguage::English);
}
}