chore: use @plugin instead of $plugin for plaintext mentions (#13921)
change plaintext plugin-mentions from `$plugin` to `@plugin`, ensure TUI can correctly decode these from history. tested locally, added/updated tests.
This commit is contained in:
parent
bf5c2f48a5
commit
590cfa6176
6 changed files with 98 additions and 16 deletions
|
|
@ -55,6 +55,7 @@ pub use mcp_connection_manager::SandboxState;
|
|||
pub use text_encoding::bytes_to_string_smart;
|
||||
mod mcp_tool_call;
|
||||
mod memories;
|
||||
pub mod mention_syntax;
|
||||
mod mentions;
|
||||
mod message_history;
|
||||
mod model_provider_info;
|
||||
|
|
|
|||
4
codex-rs/core/src/mention_syntax.rs
Normal file
4
codex-rs/core/src/mention_syntax.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// Default plaintext sigil for tools.
|
||||
pub const TOOL_MENTION_SIGIL: char = '$';
|
||||
// Plugins use `@` in linked plaintext outside TUI.
|
||||
pub const PLUGIN_TEXT_MENTION_SIGIL: char = '@';
|
||||
|
|
@ -5,11 +5,13 @@ use std::path::PathBuf;
|
|||
use codex_protocol::user_input::UserInput;
|
||||
|
||||
use crate::connectors;
|
||||
use crate::mention_syntax::PLUGIN_TEXT_MENTION_SIGIL;
|
||||
use crate::mention_syntax::TOOL_MENTION_SIGIL;
|
||||
use crate::plugins::PluginCapabilitySummary;
|
||||
use crate::skills::SkillMetadata;
|
||||
use crate::skills::injection::ToolMentionKind;
|
||||
use crate::skills::injection::app_id_from_path;
|
||||
use crate::skills::injection::extract_tool_mentions;
|
||||
use crate::skills::injection::extract_tool_mentions_with_sigil;
|
||||
use crate::skills::injection::plugin_config_name_from_path;
|
||||
use crate::skills::injection::tool_kind_for_path;
|
||||
|
||||
|
|
@ -19,10 +21,17 @@ pub(crate) struct CollectedToolMentions {
|
|||
}
|
||||
|
||||
pub(crate) fn collect_tool_mentions_from_messages(messages: &[String]) -> CollectedToolMentions {
|
||||
collect_tool_mentions_from_messages_with_sigil(messages, TOOL_MENTION_SIGIL)
|
||||
}
|
||||
|
||||
fn collect_tool_mentions_from_messages_with_sigil(
|
||||
messages: &[String],
|
||||
sigil: char,
|
||||
) -> CollectedToolMentions {
|
||||
let mut plain_names = HashSet::new();
|
||||
let mut paths = HashSet::new();
|
||||
for message in messages {
|
||||
let mentions = extract_tool_mentions(message);
|
||||
let mentions = extract_tool_mentions_with_sigil(message, sigil);
|
||||
plain_names.extend(mentions.plain_names().map(str::to_string));
|
||||
paths.extend(mentions.paths().map(str::to_string));
|
||||
}
|
||||
|
|
@ -50,7 +59,7 @@ pub(crate) fn collect_explicit_app_ids(input: &[UserInput]) -> HashSet<String> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// Collect explicit structured `plugin://...` mentions.
|
||||
/// Collect explicit structured or linked `plugin://...` mentions.
|
||||
pub(crate) fn collect_explicit_plugin_mentions(
|
||||
input: &[UserInput],
|
||||
plugins: &[PluginCapabilitySummary],
|
||||
|
|
@ -73,7 +82,11 @@ pub(crate) fn collect_explicit_plugin_mentions(
|
|||
UserInput::Mention { path, .. } => Some(path.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.chain(collect_tool_mentions_from_messages(&messages).paths)
|
||||
.chain(
|
||||
// Plugin plaintext links use `@`, not the default `$` tool sigil.
|
||||
collect_tool_mentions_from_messages_with_sigil(&messages, PLUGIN_TEXT_MENTION_SIGIL)
|
||||
.paths,
|
||||
)
|
||||
.filter(|path| tool_kind_for_path(path.as_str()) == ToolMentionKind::Plugin)
|
||||
.filter_map(|path| plugin_config_name_from_path(path.as_str()).map(str::to_string))
|
||||
.collect();
|
||||
|
|
@ -222,7 +235,7 @@ mod tests {
|
|||
];
|
||||
|
||||
let mentioned = collect_explicit_plugin_mentions(
|
||||
&[text_input("use [$sample](plugin://sample@test)")],
|
||||
&[text_input("use [@sample](plugin://sample@test)")],
|
||||
&plugins,
|
||||
);
|
||||
|
||||
|
|
@ -238,7 +251,7 @@ mod tests {
|
|||
|
||||
let mentioned = collect_explicit_plugin_mentions(
|
||||
&[
|
||||
text_input("use [$sample](plugin://sample@test)"),
|
||||
text_input("use [@sample](plugin://sample@test)"),
|
||||
UserInput::Mention {
|
||||
name: "sample".to_string(),
|
||||
path: "plugin://sample@test".to_string(),
|
||||
|
|
@ -263,4 +276,16 @@ mod tests {
|
|||
|
||||
assert_eq!(mentioned, Vec::<PluginCapabilitySummary>::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_explicit_plugin_mentions_ignores_dollar_linked_plugin_mentions() {
|
||||
let plugins = vec![plugin("sample@test", "sample")];
|
||||
|
||||
let mentioned = collect_explicit_plugin_mentions(
|
||||
&[text_input("use [$sample](plugin://sample@test)")],
|
||||
&plugins,
|
||||
);
|
||||
|
||||
assert_eq!(mentioned, Vec::<PluginCapabilitySummary>::new());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use crate::analytics_client::InvocationType;
|
|||
use crate::analytics_client::SkillInvocation;
|
||||
use crate::analytics_client::TrackEventsContext;
|
||||
use crate::instructions::SkillInstructions;
|
||||
use crate::mention_syntax::TOOL_MENTION_SIGIL;
|
||||
use crate::mentions::build_skill_name_counts;
|
||||
use crate::skills::SkillMetadata;
|
||||
use codex_otel::SessionTelemetry;
|
||||
|
|
@ -232,10 +233,10 @@ pub(crate) fn normalize_skill_path(path: &str) -> &str {
|
|||
/// resource path is present, it is captured for exact path matching while also tracking
|
||||
/// the name for fallback matching.
|
||||
pub(crate) fn extract_tool_mentions(text: &str) -> ToolMentions<'_> {
|
||||
extract_tool_mentions_with_sigil(text, '$')
|
||||
extract_tool_mentions_with_sigil(text, TOOL_MENTION_SIGIL)
|
||||
}
|
||||
|
||||
fn extract_tool_mentions_with_sigil(text: &str, sigil: char) -> ToolMentions<'_> {
|
||||
pub(crate) fn extract_tool_mentions_with_sigil(text: &str, sigil: char) -> ToolMentions<'_> {
|
||||
let text_bytes = text.as_bytes();
|
||||
let mut mentioned_names: HashSet<&str> = HashSet::new();
|
||||
let mut mentioned_paths: HashSet<&str> = HashSet::new();
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use crate::skills_helpers::skill_description;
|
|||
use crate::skills_helpers::skill_display_name;
|
||||
use codex_chatgpt::connectors::AppInfo;
|
||||
use codex_core::connectors::connector_mention_slug;
|
||||
use codex_core::mention_syntax::TOOL_MENTION_SIGIL;
|
||||
use codex_core::skills::model::SkillDependencies;
|
||||
use codex_core::skills::model::SkillInterface;
|
||||
use codex_core::skills::model::SkillMetadata;
|
||||
|
|
@ -296,8 +297,6 @@ pub(crate) struct ToolMentions {
|
|||
linked_paths: HashMap<String, String>,
|
||||
}
|
||||
|
||||
const TOOL_MENTION_SIGIL: char = '$';
|
||||
|
||||
fn extract_tool_mentions_from_text(text: &str) -> ToolMentions {
|
||||
extract_tool_mentions_from_text_with_sigil(text, TOOL_MENTION_SIGIL)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use codex_core::mention_syntax::PLUGIN_TEXT_MENTION_SIGIL;
|
||||
use codex_core::mention_syntax::TOOL_MENTION_SIGIL;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) struct LinkedMention {
|
||||
pub(crate) mention: String,
|
||||
pub(crate) path: String,
|
||||
}
|
||||
|
||||
const TOOL_MENTION_SIGIL: char = '$';
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) struct DecodedHistoryText {
|
||||
pub(crate) text: String,
|
||||
|
|
@ -77,10 +78,7 @@ pub(crate) fn decode_history_mentions(text: &str) -> DecodedHistoryText {
|
|||
|
||||
while index < bytes.len() {
|
||||
if bytes[index] == b'['
|
||||
&& let Some((name, path, end_index)) =
|
||||
parse_linked_tool_mention(text, bytes, index, TOOL_MENTION_SIGIL)
|
||||
&& !is_common_env_var(name)
|
||||
&& is_tool_path(path)
|
||||
&& let Some((name, path, end_index)) = parse_history_linked_mention(text, bytes, index)
|
||||
{
|
||||
out.push(TOOL_MENTION_SIGIL);
|
||||
out.push_str(name);
|
||||
|
|
@ -105,6 +103,31 @@ pub(crate) fn decode_history_mentions(text: &str) -> DecodedHistoryText {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_history_linked_mention<'a>(
|
||||
text: &'a str,
|
||||
text_bytes: &[u8],
|
||||
start: usize,
|
||||
) -> Option<(&'a str, &'a str, usize)> {
|
||||
// TUI writes `$name`, but may read plugin `[@name](plugin://...)` links from other clients.
|
||||
if let Some(mention @ (name, path, _)) =
|
||||
parse_linked_tool_mention(text, text_bytes, start, TOOL_MENTION_SIGIL)
|
||||
&& !is_common_env_var(name)
|
||||
&& is_tool_path(path)
|
||||
{
|
||||
return Some(mention);
|
||||
}
|
||||
|
||||
if let Some(mention @ (name, path, _)) =
|
||||
parse_linked_tool_mention(text, text_bytes, start, PLUGIN_TEXT_MENTION_SIGIL)
|
||||
&& !is_common_env_var(name)
|
||||
&& path.starts_with("plugin://")
|
||||
{
|
||||
return Some(mention);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn parse_linked_tool_mention<'a>(
|
||||
text: &'a str,
|
||||
text_bytes: &[u8],
|
||||
|
|
@ -225,6 +248,35 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_history_mentions_restores_plugin_links_with_at_sigil() {
|
||||
let decoded = decode_history_mentions(
|
||||
"Use [@sample](plugin://sample@test) and [$figma](app://figma-1).",
|
||||
);
|
||||
assert_eq!(decoded.text, "Use $sample and $figma.");
|
||||
assert_eq!(
|
||||
decoded.mentions,
|
||||
vec![
|
||||
LinkedMention {
|
||||
mention: "sample".to_string(),
|
||||
path: "plugin://sample@test".to_string(),
|
||||
},
|
||||
LinkedMention {
|
||||
mention: "figma".to_string(),
|
||||
path: "app://figma-1".to_string(),
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_history_mentions_ignores_at_sigil_for_non_plugin_paths() {
|
||||
let decoded = decode_history_mentions("Use [@figma](app://figma-1).");
|
||||
|
||||
assert_eq!(decoded.text, "Use [@figma](app://figma-1).");
|
||||
assert_eq!(decoded.mentions, Vec::<LinkedMention>::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_history_mentions_links_bound_mentions_in_order() {
|
||||
let text = "$figma then $sample then $figma then $other";
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue