Skip to main content

vulcan_luaskills/runtime/
result.rs

1use serde::{Deserialize, Serialize};
2
3/// Unified English error message returned when a tool emits a non-string result.
4/// 当工具返回非字符串结果时,统一返回的英文错误提示。
5pub const NON_STRING_TOOL_RESULT_ERROR: &str = "Tool results must be returned as plain strings. Structured JSON or table results are not supported.";
6
7/// Stable overflow-mode enum returned from the Lua runtime to the host.
8/// Lua runtime 返回给宿主的稳定超限模式枚举。
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum ToolOverflowMode {
11    /// Suggest that the host handles overflow in truncate mode.
12    /// 超限时建议宿主按截断模式处理。
13    Truncate,
14    /// Suggest that the host handles overflow in page mode.
15    /// 超限时建议宿主按分页模式处理。
16    Page,
17}
18
19impl ToolOverflowMode {
20    /// Parse an overflow mode string returned from Lua.
21    /// 解析来自 Lua 的超限模式字符串。
22    pub fn parse(value: &str) -> Option<Self> {
23        match value.trim() {
24            "truncate" => Some(Self::Truncate),
25            "page" => Some(Self::Page),
26            _ => None,
27        }
28    }
29}
30
31/// Unified intermediate result object returned from the Lua runtime to the host.
32/// Lua runtime 返回给宿主的统一中间结果对象。
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub struct RuntimeInvocationResult {
35    /// Tool body content, which must always be a string.
36    /// 工具正文内容,必须始终为字符串。
37    pub content: String,
38    /// Optional overflow mode; when absent the host applies its own default policy.
39    /// 可选超限模式;为空时由宿主按自身默认策略处理。
40    pub overflow_mode: Option<ToolOverflowMode>,
41    /// Optional template hint used only as a host-side suggestion, never rendered directly by the runtime.
42    /// 可选模板建议名,仅作为宿主层提示,不在 runtime 中直接渲染。
43    pub template_hint: Option<String>,
44    /// Normalized body byte count used by the host to decide pagination, truncation, or compression.
45    /// 正文规范化后的字节数,供宿主判断是否需要分页、截断或压缩。
46    pub content_bytes: usize,
47    /// Normalized body line count used by the host to decide whether the line budget is exceeded.
48    /// 正文规范化后的行数,供宿主判断是否命中行预算。
49    pub content_lines: usize,
50}
51
52impl RuntimeInvocationResult {
53    /// Build the unified runtime result from content and optional overflow hints while computing byte and line metrics at creation time.
54    /// 根据正文和可选超限提示构造统一运行时结果,并在创建时计算字节与行数。
55    pub fn from_content_parts(
56        content: String,
57        overflow_mode: Option<ToolOverflowMode>,
58        template_hint: Option<String>,
59    ) -> Self {
60        let normalized = normalize_text(&content);
61        let content_bytes = normalized.len();
62        let content_lines = split_lines(&normalized).len();
63        Self {
64            content,
65            overflow_mode,
66            template_hint,
67            content_bytes,
68            content_lines,
69        }
70    }
71
72    /// Build a content-only string result.
73    /// 构造只包含正文的字符串返回值。
74    pub fn plain(content: String) -> Self {
75        Self::from_content_parts(content, None, None)
76    }
77}
78
79/// Normalize line endings so byte and line metrics are computed consistently.
80/// 规范化文本中的换行,统一统计字节与行数。
81fn normalize_text(text: &str) -> String {
82    text.replace("\r\n", "\n").replace('\r', "\n")
83}
84
85/// Split text into lines after newline normalization.
86/// 按规范化后的换行拆分文本行。
87fn split_lines(text: &str) -> Vec<&str> {
88    if text.is_empty() {
89        Vec::new()
90    } else {
91        text.split('\n').collect()
92    }
93}