mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
Align message-board tools with the multi-agent namespace (#47028)
## Why Message-board tools hardcoded the `collaboration` namespace and used a different namespace description from the multi-agent tools. This ignored custom namespace configuration and caused conflicts under strict tool collision checking. ## What changed - Use `multi_agent_v2.tool_namespace` and the shared multi-agent namespace description for message-board tools, resolving the namespace at runtime startup. - Support unnamespaced tool specifications when no namespace is supplied. - Remove hardcoded `collaboration` prefixes from message-board notifications. ## Testing Add app-server integration coverage for the default `collaboration` namespace and a configured `agents` namespace with strict collision checking enabled. Verify that both tool families are advertised together and that `get_channels` executes through the installed extension. GitOrigin-RevId: fef7be51ea2a84bbdb6d3491606db442ed4e6ea3
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
//!
|
||||
//! Backend selection is a host concern. Startup failures leave tools unavailable
|
||||
//! and emit a warning; a disabled factory does not open storage.
|
||||
//! Tool namespaces follow the host's configuration at runtime startup.
|
||||
|
||||
use crate::AgentMessageBoard;
|
||||
use crate::message_board_tools;
|
||||
@@ -26,15 +27,19 @@ use std::sync::Arc;
|
||||
type BoardFactory<C> = dyn Fn(&C, SessionId, ThreadId) -> BoxFuture<'static, Result<Option<Arc<dyn AgentMessageBoard>>>>
|
||||
+ Send
|
||||
+ Sync;
|
||||
type NamespaceResolver<C> = dyn Fn(&C) -> Option<String> + Send + Sync;
|
||||
|
||||
struct BoardExtension<C> {
|
||||
open: Box<BoardFactory<C>>,
|
||||
tool_namespace: Box<NamespaceResolver<C>>,
|
||||
namespace_description: &'static str,
|
||||
events: Arc<dyn ExtensionEventSink>,
|
||||
}
|
||||
struct Binding {
|
||||
board: Arc<dyn AgentMessageBoard>,
|
||||
caller: ThreadId,
|
||||
path: AgentPath,
|
||||
namespace: Option<String>,
|
||||
}
|
||||
|
||||
impl<C: Sync> ThreadLifecycleContributor<C> for BoardExtension<C> {
|
||||
@@ -58,6 +63,7 @@ impl<C: Sync> ThreadLifecycleContributor<C> for BoardExtension<C> {
|
||||
input.thread_store.insert(Binding {
|
||||
board,
|
||||
caller,
|
||||
namespace: (self.tool_namespace)(input.config),
|
||||
path: input
|
||||
.session_source
|
||||
.get_agent_path()
|
||||
@@ -86,7 +92,13 @@ impl<C: Sync> ToolContributor for BoardExtension<C> {
|
||||
thread_store
|
||||
.get::<Binding>()
|
||||
.map_or_else(Vec::new, |binding| {
|
||||
message_board_tools(binding.board.clone(), binding.caller, binding.path.clone())
|
||||
message_board_tools(
|
||||
binding.board.clone(),
|
||||
binding.caller,
|
||||
binding.path.clone(),
|
||||
binding.namespace.as_deref(),
|
||||
self.namespace_description,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -94,8 +106,12 @@ impl<C: Sync> ToolContributor for BoardExtension<C> {
|
||||
/// Installs message-board lifecycle and tool contributions. The factory selects
|
||||
/// a local or remote backend, or returns None when disabled. Configuration is
|
||||
/// read at runtime startup, including resume; no board is created by installation.
|
||||
/// The host supplies its shared namespace description and resolves the namespace
|
||||
/// name from the runtime's startup configuration.
|
||||
pub fn install<C: Sync + 'static>(
|
||||
registry: &mut ExtensionRegistryBuilder<C>,
|
||||
namespace_description: &'static str,
|
||||
tool_namespace: impl Fn(&C) -> Option<String> + Send + Sync + 'static,
|
||||
open: impl Fn(
|
||||
&C,
|
||||
SessionId,
|
||||
@@ -107,6 +123,8 @@ pub fn install<C: Sync + 'static>(
|
||||
) {
|
||||
let extension = Arc::new(BoardExtension {
|
||||
open: Box::new(open),
|
||||
tool_namespace: Box::new(tool_namespace),
|
||||
namespace_description,
|
||||
events: registry.event_sink(),
|
||||
});
|
||||
registry.thread_lifecycle_contributor(extension.clone());
|
||||
|
||||
@@ -39,10 +39,13 @@ const MAX_RESPONSE_BYTES: usize = 8_000;
|
||||
|
||||
/// Creates tools without registering or enabling the extension.
|
||||
/// The caller and its path must come from the host's authoritative tree metadata.
|
||||
/// Namespace metadata must match the host's other multi-agent tools.
|
||||
pub fn message_board_tools(
|
||||
board: Arc<dyn AgentMessageBoard>,
|
||||
caller: ThreadId,
|
||||
caller_path: AgentPath,
|
||||
namespace: Option<&str>,
|
||||
namespace_description: &str,
|
||||
) -> Vec<Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>> {
|
||||
spec::NAMES
|
||||
.into_iter()
|
||||
@@ -52,6 +55,8 @@ pub fn message_board_tools(
|
||||
caller,
|
||||
caller_path: caller_path.clone(),
|
||||
name,
|
||||
namespace: namespace.map(str::to_owned),
|
||||
namespace_description: namespace_description.to_owned(),
|
||||
}) as Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>
|
||||
})
|
||||
.collect()
|
||||
@@ -62,14 +67,20 @@ struct BoardTool {
|
||||
caller: ThreadId,
|
||||
caller_path: AgentPath,
|
||||
name: &'static str,
|
||||
namespace: Option<String>,
|
||||
namespace_description: String,
|
||||
}
|
||||
|
||||
impl<'call> ToolExecutor<ToolCall<'call>> for BoardTool {
|
||||
fn tool_name(&self) -> ToolName {
|
||||
ToolName::namespaced("collaboration", self.name)
|
||||
ToolName::new(self.namespace.clone(), self.name)
|
||||
}
|
||||
fn spec(&self) -> ToolSpec {
|
||||
spec::tool(self.name)
|
||||
spec::tool(
|
||||
self.name,
|
||||
self.namespace.as_deref(),
|
||||
&self.namespace_description,
|
||||
)
|
||||
}
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
matches!(
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
//! Schemas for the shared discussion tools, in the existing collaboration namespace.
|
||||
//! Schemas for shared discussion tools. The host supplies their namespace.
|
||||
|
||||
use codex_tools::ResponsesApiNamespace;
|
||||
use codex_tools::ResponsesApiNamespaceTool;
|
||||
use codex_tools::ResponsesApiTool;
|
||||
use codex_tools::ToolSpec;
|
||||
use codex_tools::default_namespace_description;
|
||||
use codex_tools::parse_tool_input_schema;
|
||||
use serde_json::json;
|
||||
|
||||
@@ -20,7 +19,7 @@ pub(super) const NAMES: [&str; 9] = [
|
||||
"post",
|
||||
];
|
||||
|
||||
pub(super) fn tool(name: &str) -> ToolSpec {
|
||||
pub(super) fn tool(name: &str, namespace: Option<&str>, namespace_description: &str) -> ToolSpec {
|
||||
let (description, fields, required): (&str, &[&str], &[&str]) = match name {
|
||||
"create_channel" => (
|
||||
"Create a channel shared by this agent tree. Subscribe to new discussion roots by default.",
|
||||
@@ -111,17 +110,21 @@ pub(super) fn tool(name: &str) -> ToolSpec {
|
||||
properties.insert((*field).into(), schema);
|
||||
}
|
||||
let parameters = json!({"type":"object","properties":properties,"required":required,"additionalProperties":false});
|
||||
ToolSpec::Namespace(ResponsesApiNamespace {
|
||||
name: "collaboration".into(),
|
||||
description: default_namespace_description("collaboration"),
|
||||
tools: vec![ResponsesApiNamespaceTool::Function(ResponsesApiTool {
|
||||
name: name.into(),
|
||||
description: description.into(),
|
||||
strict: false,
|
||||
defer_loading: None,
|
||||
parameters: parse_tool_input_schema(¶meters)
|
||||
.unwrap_or_else(|error| panic!("message-board schema must parse: {error}")),
|
||||
output_schema: None,
|
||||
})],
|
||||
})
|
||||
let tool = ResponsesApiTool {
|
||||
name: name.into(),
|
||||
description: description.into(),
|
||||
strict: false,
|
||||
defer_loading: None,
|
||||
parameters: parse_tool_input_schema(¶meters)
|
||||
.unwrap_or_else(|error| panic!("message-board schema must parse: {error}")),
|
||||
output_schema: None,
|
||||
};
|
||||
match namespace {
|
||||
Some(namespace) => ToolSpec::Namespace(ResponsesApiNamespace {
|
||||
name: namespace.into(),
|
||||
description: namespace_description.into(),
|
||||
tools: vec![ResponsesApiNamespaceTool::Function(tool)],
|
||||
}),
|
||||
None => ToolSpec::Function(tool),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -630,7 +630,13 @@ async fn tools_cover_channel_discussions_subscriptions_and_escaped_previews() {
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
let tools = message_board_tools(board, root, AgentPath::root());
|
||||
let tools = message_board_tools(
|
||||
board,
|
||||
root,
|
||||
AgentPath::root(),
|
||||
Some("collaboration"),
|
||||
"Shared tools",
|
||||
);
|
||||
let tool = |name: &str| {
|
||||
tools
|
||||
.iter()
|
||||
@@ -821,7 +827,13 @@ async fn tools_validate_arguments_deduplicate_calls_and_bound_unicode_results()
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
let tools = message_board_tools(board.clone(), root, AgentPath::root());
|
||||
let tools = message_board_tools(
|
||||
board.clone(),
|
||||
root,
|
||||
AgentPath::root(),
|
||||
Some("collaboration"),
|
||||
"Shared tools",
|
||||
);
|
||||
let call = board_tool_call;
|
||||
let tool = |name: &str| {
|
||||
tools
|
||||
|
||||
Reference in New Issue
Block a user