Files
codex/codex-rs/protocol/src/tool_name.rs
pakrym-oai 61142b6169 Remove ToolName display helper (#21465)
## Why

`ToolName::display()` made it too easy to flatten tool identity and
accidentally compare rendered strings. Tool identity should stay
structural until a legacy string boundary actually requires the
flattened spelling.

## What

- Removes `ToolName::display()` and relies on the existing `Display`
impl for messages and errors.
- Adds structural ordering for `ToolName` and uses it for
sorting/deduping deferred tools.
- Carries `ToolName` through tool/sandbox plumbing, flattening only at
legacy boundaries such as hook payloads, telemetry tags, and Responses
tool names.
- Updates MCP normalization tests to assert `ToolName` structure instead
of rendered strings.

## Testing

- `cargo test -p codex-mcp test_normalize_tools`
- `cargo test -p codex-core unavailable_tool`
- `just fix -p codex-protocol`
- `just fix -p codex-mcp`
- `just fix -p codex-core`
2026-05-08 12:17:48 -07:00

77 lines
1.9 KiB
Rust

use serde::Deserialize;
use serde::Serialize;
use std::cmp::Ordering;
use std::fmt;
/// Identifies a callable tool, preserving the namespace split when the model
/// provides one.
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct ToolName {
pub name: String,
pub namespace: Option<String>,
}
impl ToolName {
pub fn new(namespace: Option<String>, name: impl Into<String>) -> Self {
Self {
name: name.into(),
namespace,
}
}
pub fn plain(name: impl Into<String>) -> Self {
Self {
name: name.into(),
namespace: None,
}
}
pub fn namespaced(namespace: impl Into<String>, name: impl Into<String>) -> Self {
Self {
name: name.into(),
namespace: Some(namespace.into()),
}
}
}
impl fmt::Display for ToolName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.namespace {
Some(namespace) => write!(f, "{namespace}{}", self.name),
None => f.write_str(&self.name),
}
}
}
impl Ord for ToolName {
fn cmp(&self, other: &Self) -> Ordering {
let lhs = match &self.namespace {
Some(namespace) => (namespace.as_str(), Some(self.name.as_str())),
None => (self.name.as_str(), None),
};
let rhs = match &other.namespace {
Some(namespace) => (namespace.as_str(), Some(other.name.as_str())),
None => (other.name.as_str(), None),
};
lhs.cmp(&rhs)
}
}
impl PartialOrd for ToolName {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl From<String> for ToolName {
fn from(name: String) -> Self {
Self::plain(name)
}
}
impl From<&str> for ToolName {
fn from(name: &str) -> Self {
Self::plain(name)
}
}