mirror of
https://github.com/openai/codex.git
synced 2026-10-22 17:58:46 +00:00
v1
This commit is contained in:
Generated
+35
@@ -2780,6 +2780,16 @@ dependencies = [
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-extension-api"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-git-attribution",
|
||||
"codex-memories",
|
||||
"codex-multi-agent-v2",
|
||||
"rmcp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-external-agent-migration"
|
||||
version = "0.0.0"
|
||||
@@ -2858,6 +2868,13 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-git-attribution"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-extension-api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-git-utils"
|
||||
version = "0.0.0"
|
||||
@@ -3065,6 +3082,17 @@ dependencies = [
|
||||
"wiremock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-memories"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-extension-api",
|
||||
"codex-memories-read",
|
||||
"codex-utils-absolute-path",
|
||||
"rmcp",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-memories-mcp"
|
||||
version = "0.0.0"
|
||||
@@ -3206,6 +3234,13 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-multi-agent-v2"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-extension-api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-network-proxy"
|
||||
version = "0.0.0"
|
||||
|
||||
@@ -45,6 +45,10 @@ members = [
|
||||
"exec-server",
|
||||
"execpolicy",
|
||||
"execpolicy-legacy",
|
||||
"ext/extension-api",
|
||||
"ext/git-attribution",
|
||||
"ext/memories",
|
||||
"ext/multi-agent-v2",
|
||||
"external-agent-migration",
|
||||
"external-agent-sessions",
|
||||
"keyring-store",
|
||||
@@ -159,10 +163,14 @@ codex-exec = { path = "exec" }
|
||||
codex-file-system = { path = "file-system" }
|
||||
codex-exec-server = { path = "exec-server" }
|
||||
codex-execpolicy = { path = "execpolicy" }
|
||||
codex-extension-api = { path = "ext/extension-api" }
|
||||
codex-external-agent-migration = { path = "external-agent-migration" }
|
||||
codex-external-agent-sessions = { path = "external-agent-sessions" }
|
||||
codex-experimental-api-macros = { path = "codex-experimental-api-macros" }
|
||||
codex-features = { path = "features" }
|
||||
codex-git-attribution = { path = "ext/git-attribution" }
|
||||
codex-memories = { path = "ext/memories" }
|
||||
codex-multi-agent-v2 = { path = "ext/multi-agent-v2" }
|
||||
codex-feedback = { path = "feedback" }
|
||||
codex-install-context = { path = "install-context" }
|
||||
codex-file-search = { path = "file-search" }
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
codex_rust_crate(
|
||||
name = "extension-api",
|
||||
crate_name = "codex_extension_api",
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
name = "codex-extension-api"
|
||||
version.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "codex_extension_api"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
rmcp = { workspace = true, default-features = false, features = [
|
||||
"schemars",
|
||||
"server",
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
codex-git-attribution = { workspace = true }
|
||||
codex-memories = { workspace = true }
|
||||
codex-multi-agent-v2 = { workspace = true }
|
||||
@@ -0,0 +1,113 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_extension_api::ExtensionRegistryBuilder;
|
||||
use codex_git_attribution as git_attribution;
|
||||
use codex_git_attribution::GitAttributionContext;
|
||||
use codex_memories::MemoriesContext;
|
||||
use codex_memories::MemoriesExtension;
|
||||
use codex_multi_agent_v2 as multi_agent_v2;
|
||||
use codex_multi_agent_v2::MultiAgentV2Context;
|
||||
use codex_multi_agent_v2::UsageHintAudience;
|
||||
|
||||
fn main() {
|
||||
let registry = ExtensionRegistryBuilder::<ctx::RuntimeContext>::new()
|
||||
.with_extension(git_attribution::extension())
|
||||
.with_extension(Arc::new(MemoriesExtension::new(Some(
|
||||
"Please use FS access bla bla bla.".to_string(),
|
||||
))))
|
||||
.with_extension(multi_agent_v2::extension())
|
||||
.build();
|
||||
|
||||
let root_context = ctx::RuntimeContext {
|
||||
commit_attribution: None,
|
||||
memory_tool_enabled: true,
|
||||
use_memories: true,
|
||||
multi_agent_v2_enabled: true,
|
||||
multi_agent_v2_usage_hint_audience: UsageHintAudience::RootAgent,
|
||||
root_agent_usage_hint_text: Some("Root-agent usage hint.".to_string()),
|
||||
subagent_usage_hint_text: Some("Subagent usage hint.".to_string()),
|
||||
};
|
||||
let memories_disabled_context = ctx::RuntimeContext {
|
||||
use_memories: false,
|
||||
..root_context.clone()
|
||||
};
|
||||
|
||||
// Build the prompt
|
||||
let prompt_fragments = registry
|
||||
.prompt_contributors()
|
||||
.iter()
|
||||
.flat_map(|contributor| contributor.contribute(&root_context))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Get tools (MCP here but this should shift to handlers)
|
||||
let tools = registry
|
||||
.mcp_tool_contributors()
|
||||
.iter()
|
||||
.flat_map(|contributor| contributor.tools(&root_context))
|
||||
.collect::<Vec<_>>();
|
||||
let tools_without_memories = registry
|
||||
.mcp_tool_contributors()
|
||||
.iter()
|
||||
.flat_map(|contributor| contributor.tools(&memories_disabled_context))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
println!("prompt fragments: {}", prompt_fragments.len());
|
||||
println!("mcp tools: {}", tools.len());
|
||||
println!(
|
||||
"mcp tools when use_memories=false: {}",
|
||||
tools_without_memories.len()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
mod ctx {
|
||||
use codex_git_attribution::GitAttributionContext;
|
||||
use codex_memories::MemoriesContext;
|
||||
use codex_multi_agent_v2::{MultiAgentV2Context, UsageHintAudience};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RuntimeContext {
|
||||
// Ideally this should be at the config layer instead
|
||||
pub commit_attribution: Option<String>,
|
||||
pub memory_tool_enabled: bool,
|
||||
pub use_memories: bool,
|
||||
pub multi_agent_v2_enabled: bool,
|
||||
pub multi_agent_v2_usage_hint_audience: UsageHintAudience,
|
||||
pub root_agent_usage_hint_text: Option<String>,
|
||||
pub subagent_usage_hint_text: Option<String>,
|
||||
}
|
||||
|
||||
impl GitAttributionContext for RuntimeContext {
|
||||
fn commit_attribution(&self) -> Option<&str> {
|
||||
self.commit_attribution.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoriesContext for RuntimeContext {
|
||||
fn memory_tool_enabled(&self) -> bool {
|
||||
self.memory_tool_enabled
|
||||
}
|
||||
|
||||
fn use_memories(&self) -> bool {
|
||||
self.use_memories
|
||||
}
|
||||
}
|
||||
|
||||
impl MultiAgentV2Context for RuntimeContext {
|
||||
fn multi_agent_v2_enabled(&self) -> bool {
|
||||
self.multi_agent_v2_enabled
|
||||
}
|
||||
|
||||
fn usage_hint_audience(&self) -> UsageHintAudience {
|
||||
self.multi_agent_v2_usage_hint_audience
|
||||
}
|
||||
|
||||
fn root_agent_usage_hint_text(&self) -> Option<&str> {
|
||||
self.root_agent_usage_hint_text.as_deref()
|
||||
}
|
||||
|
||||
fn subagent_usage_hint_text(&self) -> Option<&str> {
|
||||
self.subagent_usage_hint_text.as_deref()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//! Runtime contribution ports exposed by the extension API.
|
||||
//!
|
||||
//! Extensions can install several independent contribution types. This module
|
||||
//! is the small public façade over those ports: callers can import the common
|
||||
//! vocabulary from one place, while each contribution family keeps its own
|
||||
//! supporting types nearby.
|
||||
|
||||
mod prompt;
|
||||
|
||||
pub use prompt::PromptFragment;
|
||||
pub use prompt::PromptSlot;
|
||||
|
||||
use rmcp::model::Tool;
|
||||
|
||||
/// Extension contribution that adds prompt fragments during prompt assembly.
|
||||
///
|
||||
/// Implementations should inspect only their feature-owned slice of the
|
||||
/// current runtime context and describe the prompt content exposed for that
|
||||
/// invocation. The host remains responsible for ordering fragments and
|
||||
/// assembling prompt items.
|
||||
pub trait PromptContributor<C>: Send + Sync {
|
||||
fn contribute(&self, context: &C) -> Vec<PromptFragment>;
|
||||
}
|
||||
|
||||
/// Extension contribution that exposes MCP tool definitions owned by a feature.
|
||||
///
|
||||
/// Implementations should inspect only their feature-owned slice of the
|
||||
/// current runtime context and return the tools exposed for that invocation.
|
||||
/// The host remains responsible for mounting those tools and routing
|
||||
/// execution.
|
||||
///
|
||||
/// This is intentionally MCP-shaped for now because the more general tool
|
||||
/// abstraction has not been extracted yet.
|
||||
pub trait McpToolContributor<C>: Send + Sync {
|
||||
fn tools(&self, context: &C) -> Vec<Tool>;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/// Prompt region targeted by a contributed fragment.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum PromptSlot {
|
||||
DeveloperPolicy,
|
||||
DeveloperCapabilities,
|
||||
ContextualUser,
|
||||
SeparateDeveloper,
|
||||
}
|
||||
|
||||
/// One text fragment contributed to prompt assembly by an extension. (wiring not done yet on purpose)
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct PromptFragment {
|
||||
slot: PromptSlot,
|
||||
text: String,
|
||||
}
|
||||
|
||||
impl PromptFragment {
|
||||
/// Creates a prompt fragment for the given slot.
|
||||
pub fn new(slot: PromptSlot, text: impl Into<String>) -> Self {
|
||||
Self {
|
||||
slot,
|
||||
text: text.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a developer-policy prompt fragment.
|
||||
pub fn developer_policy(text: impl Into<String>) -> Self {
|
||||
Self::new(PromptSlot::DeveloperPolicy, text)
|
||||
}
|
||||
|
||||
/// Creates a developer-capabilities prompt fragment.
|
||||
pub fn developer_capability(text: impl Into<String>) -> Self {
|
||||
Self::new(PromptSlot::DeveloperCapabilities, text)
|
||||
}
|
||||
|
||||
/// Creates a separate top-level developer prompt fragment.
|
||||
pub fn separate_developer(text: impl Into<String>) -> Self {
|
||||
Self::new(PromptSlot::SeparateDeveloper, text)
|
||||
}
|
||||
|
||||
/// Returns the target prompt slot.
|
||||
pub fn slot(&self) -> PromptSlot {
|
||||
self.slot
|
||||
}
|
||||
|
||||
/// Returns the model-visible text.
|
||||
pub fn text(&self) -> &str {
|
||||
&self.text
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::ExtensionRegistryBuilder;
|
||||
|
||||
/// First-party extension that can install one or more typed runtime contributions.
|
||||
///
|
||||
/// Implementations should use [`Self::install`] only to register the concrete
|
||||
/// providers they own.
|
||||
pub trait CodexExtension<C>: Send + Sync {
|
||||
/// Registers this extension's concrete typed contributions.
|
||||
fn install(self: Arc<Self>, registry: &mut ExtensionRegistryBuilder<C>);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//! Semantic ports and typed assembly for first-party Codex extensions.
|
||||
//!
|
||||
//! This crate owns the small stable assembly layer: host-independent semantic
|
||||
//! ports plus the registry used to collect their implementations. Port
|
||||
//! contracts should live here only once they can be expressed without pulling
|
||||
//! `codex-core` internals across the boundary.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod contributors;
|
||||
mod extension;
|
||||
mod registry;
|
||||
|
||||
pub use contributors::McpToolContributor;
|
||||
pub use contributors::PromptContributor;
|
||||
pub use contributors::PromptFragment;
|
||||
pub use contributors::PromptSlot;
|
||||
pub use extension::CodexExtension;
|
||||
pub use registry::ExtensionRegistry;
|
||||
pub use registry::ExtensionRegistryBuilder;
|
||||
@@ -0,0 +1,81 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::CodexExtension;
|
||||
use crate::McpToolContributor;
|
||||
use crate::PromptContributor;
|
||||
|
||||
/// Mutable registry used while extensions install their typed contributions.
|
||||
pub struct ExtensionRegistryBuilder<C> {
|
||||
mcp_tool_contributors: Vec<Arc<dyn McpToolContributor<C>>>,
|
||||
prompt_contributors: Vec<Arc<dyn PromptContributor<C>>>,
|
||||
}
|
||||
|
||||
impl<C> Default for ExtensionRegistryBuilder<C> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mcp_tool_contributors: Vec::new(),
|
||||
prompt_contributors: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> ExtensionRegistryBuilder<C> {
|
||||
/// Creates an empty registry builder.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Installs one extension and returns the builder.
|
||||
#[must_use]
|
||||
pub fn with_extension<E>(mut self, extension: Arc<E>) -> Self
|
||||
where
|
||||
E: CodexExtension<C> + 'static,
|
||||
{
|
||||
self.install_extension(extension);
|
||||
self
|
||||
}
|
||||
|
||||
/// Installs one extension into the registry under construction.
|
||||
pub fn install_extension<E>(&mut self, extension: Arc<E>)
|
||||
where
|
||||
E: CodexExtension<C> + 'static,
|
||||
{
|
||||
extension.install(self);
|
||||
}
|
||||
|
||||
/// Registers one MCP tool contributor.
|
||||
pub fn mcp_tool_contributor(&mut self, contributor: Arc<dyn McpToolContributor<C>>) {
|
||||
self.mcp_tool_contributors.push(contributor);
|
||||
}
|
||||
|
||||
/// Registers one prompt contributor.
|
||||
pub fn prompt_contributor(&mut self, contributor: Arc<dyn PromptContributor<C>>) {
|
||||
self.prompt_contributors.push(contributor);
|
||||
}
|
||||
|
||||
/// Finishes construction and returns the immutable registry.
|
||||
pub fn build(self) -> ExtensionRegistry<C> {
|
||||
ExtensionRegistry {
|
||||
mcp_tool_contributors: self.mcp_tool_contributors,
|
||||
prompt_contributors: self.prompt_contributors,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Immutable typed registry produced after extensions are installed.
|
||||
pub struct ExtensionRegistry<C> {
|
||||
mcp_tool_contributors: Vec<Arc<dyn McpToolContributor<C>>>,
|
||||
prompt_contributors: Vec<Arc<dyn PromptContributor<C>>>,
|
||||
}
|
||||
|
||||
impl<C> ExtensionRegistry<C> {
|
||||
/// Returns the registered MCP tool contributors.
|
||||
pub fn mcp_tool_contributors(&self) -> &[Arc<dyn McpToolContributor<C>>] {
|
||||
&self.mcp_tool_contributors
|
||||
}
|
||||
|
||||
/// Returns the registered prompt contributors.
|
||||
pub fn prompt_contributors(&self) -> &[Arc<dyn PromptContributor<C>>] {
|
||||
&self.prompt_contributors
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
codex_rust_crate(
|
||||
name = "git-attribution",
|
||||
crate_name = "codex_git_attribution",
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
name = "codex-git-attribution"
|
||||
version.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "codex_git_attribution"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codex-extension-api = { workspace = true }
|
||||
@@ -0,0 +1,79 @@
|
||||
//! Git-attribution prompt contribution packaged as a Codex extension.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_extension_api::CodexExtension;
|
||||
use codex_extension_api::ExtensionRegistryBuilder;
|
||||
use codex_extension_api::PromptContributor;
|
||||
use codex_extension_api::PromptFragment;
|
||||
|
||||
const DEFAULT_ATTRIBUTION_VALUE: &str = "Codex <noreply@openai.com>";
|
||||
|
||||
/// Runtime facts needed to render the git-attribution prompt contribution.
|
||||
///
|
||||
/// Hosts should return the current effective attribution override for the
|
||||
/// thread being assembled. `None` selects Codex's default attribution, while a
|
||||
/// blank string disables the contribution.
|
||||
pub trait GitAttributionContext {
|
||||
fn commit_attribution(&self) -> Option<&str>;
|
||||
}
|
||||
|
||||
/// Prompt-only extension that contributes the configured git-attribution instruction.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct GitAttributionExtension;
|
||||
|
||||
impl GitAttributionExtension {
|
||||
/// Creates an extension instance.
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// Returns the model-visible trailer instruction, if attribution is enabled.
|
||||
pub fn instruction<C: GitAttributionContext>(&self, context: &C) -> Option<String> {
|
||||
let trailer = build_commit_message_trailer(context.commit_attribution())?;
|
||||
Some(format!(
|
||||
"When you write or edit a git commit message, ensure the message ends with this trailer exactly once:\n{trailer}\n\nRules:\n- Keep existing trailers and append this trailer at the end if missing.\n- Do not duplicate this trailer if it already exists.\n- Keep one blank line between the commit body and trailer block."
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: GitAttributionContext> PromptContributor<C> for GitAttributionExtension {
|
||||
fn contribute(&self, context: &C) -> Vec<PromptFragment> {
|
||||
self.instruction(context)
|
||||
.map(PromptFragment::developer_capability)
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: GitAttributionContext> CodexExtension<C> for GitAttributionExtension {
|
||||
fn install(self: Arc<Self>, registry: &mut ExtensionRegistryBuilder<C>) {
|
||||
registry.prompt_contributor(self);
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a shared git-attribution extension instance.
|
||||
pub fn extension() -> Arc<GitAttributionExtension> {
|
||||
Arc::new(GitAttributionExtension::new())
|
||||
}
|
||||
|
||||
fn build_commit_message_trailer(config_attribution: Option<&str>) -> Option<String> {
|
||||
let value = resolve_attribution_value(config_attribution)?;
|
||||
Some(format!("Co-authored-by: {value}"))
|
||||
}
|
||||
|
||||
fn resolve_attribution_value(config_attribution: Option<&str>) -> Option<String> {
|
||||
match config_attribution {
|
||||
Some(value) => {
|
||||
let trimmed = value.trim();
|
||||
if trimmed.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(trimmed.to_string())
|
||||
}
|
||||
}
|
||||
None => Some(DEFAULT_ATTRIBUTION_VALUE.to_string()),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
codex_rust_crate(
|
||||
name = "memories",
|
||||
crate_name = "codex_memories",
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
name = "codex-memories"
|
||||
version.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "codex_memories"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codex-extension-api = { workspace = true }
|
||||
codex-memories-read = { workspace = true }
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
rmcp = { workspace = true, default-features = false, features = [
|
||||
"schemars",
|
||||
"server",
|
||||
] }
|
||||
serde_json = { workspace = true }
|
||||
@@ -0,0 +1,103 @@
|
||||
//! Memories tools and prompt contribution packaged as a Codex extension.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_extension_api::CodexExtension;
|
||||
use codex_extension_api::ExtensionRegistryBuilder;
|
||||
use codex_extension_api::McpToolContributor;
|
||||
use codex_extension_api::PromptContributor;
|
||||
use codex_extension_api::PromptFragment;
|
||||
use codex_memories_read::build_memory_tool_developer_instructions;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use rmcp::model::Tool;
|
||||
use rmcp::model::ToolAnnotations;
|
||||
use serde_json::Map;
|
||||
use serde_json::Value;
|
||||
|
||||
/// Runtime facts needed to decide whether read-memory surfaces are visible.
|
||||
///
|
||||
/// Hosts should expose the current effective values for the thread being
|
||||
/// assembled. The extension owns the policy that combines those values.
|
||||
pub trait MemoriesContext {
|
||||
fn memory_tool_enabled(&self) -> bool;
|
||||
fn use_memories(&self) -> bool;
|
||||
}
|
||||
|
||||
/// Extension that contributes memories MCP tools plus their read-path guidance.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MemoriesExtension {
|
||||
read_prompt: Option<String>,
|
||||
}
|
||||
|
||||
impl MemoriesExtension {
|
||||
/// Creates an extension from a pre-rendered read prompt.
|
||||
pub fn new(read_prompt: Option<String>) -> Self {
|
||||
Self { read_prompt }
|
||||
}
|
||||
|
||||
/// Creates an extension using the live memories read prompt for this Codex home.
|
||||
pub async fn from_codex_home(codex_home: &AbsolutePathBuf) -> Self {
|
||||
Self {
|
||||
read_prompt: build_memory_tool_developer_instructions(codex_home).await,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the rendered developer instruction for read access, if available.
|
||||
pub fn read_prompt(&self) -> Option<&str> {
|
||||
self.read_prompt.as_deref()
|
||||
}
|
||||
|
||||
fn is_read_surface_enabled<C: MemoriesContext>(&self, context: &C) -> bool {
|
||||
context.memory_tool_enabled() && context.use_memories()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: MemoriesContext> McpToolContributor<C> for MemoriesExtension {
|
||||
fn tools(&self, context: &C) -> Vec<Tool> {
|
||||
if !self.is_read_surface_enabled(context) {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
vec![
|
||||
simple_tool("list", "List memory entries."),
|
||||
simple_tool("read", "Read one memory entry."),
|
||||
simple_tool("search", "Search memory entries."),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: MemoriesContext> PromptContributor<C> for MemoriesExtension {
|
||||
fn contribute(&self, context: &C) -> Vec<PromptFragment> {
|
||||
if !self.is_read_surface_enabled(context) {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
self.read_prompt()
|
||||
.map(PromptFragment::developer_policy)
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: MemoriesContext> CodexExtension<C> for MemoriesExtension {
|
||||
fn install(self: Arc<Self>, registry: &mut ExtensionRegistryBuilder<C>) {
|
||||
registry.mcp_tool_contributor(self.clone());
|
||||
registry.prompt_contributor(self);
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a shared memories extension using the live memories read prompt.
|
||||
pub async fn extension(codex_home: &AbsolutePathBuf) -> Arc<MemoriesExtension> {
|
||||
Arc::new(MemoriesExtension::from_codex_home(codex_home).await)
|
||||
}
|
||||
|
||||
fn simple_tool(name: &'static str, description: &'static str) -> Tool {
|
||||
Tool::new(name, description, simple_input_schema())
|
||||
.annotate(ToolAnnotations::new().read_only(true))
|
||||
}
|
||||
|
||||
fn simple_input_schema() -> Map<String, Value> {
|
||||
Map::from_iter([("type".to_string(), Value::String("object".to_string()))])
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
codex_rust_crate(
|
||||
name = "multi-agent-v2",
|
||||
crate_name = "codex_multi_agent_v2",
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
name = "codex-multi-agent-v2"
|
||||
version.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "codex_multi_agent_v2"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codex-extension-api = { workspace = true }
|
||||
@@ -0,0 +1,74 @@
|
||||
//! MultiAgentV2 usage-hint prompt contribution packaged as a Codex extension.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_extension_api::CodexExtension;
|
||||
use codex_extension_api::ExtensionRegistryBuilder;
|
||||
use codex_extension_api::PromptContributor;
|
||||
use codex_extension_api::PromptFragment;
|
||||
|
||||
/// Which kind of agent should receive a MultiAgentV2 usage hint.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum UsageHintAudience {
|
||||
RootAgent,
|
||||
SpawnedSubagent,
|
||||
Omitted,
|
||||
}
|
||||
|
||||
/// Runtime facts needed to render MultiAgentV2 usage-hint contributions.
|
||||
///
|
||||
/// Hosts should return the current effective settings for the thread being
|
||||
/// assembled. The extension owns the routing policy that chooses which hint, if
|
||||
/// any, is visible for that invocation.
|
||||
pub trait MultiAgentV2Context {
|
||||
fn multi_agent_v2_enabled(&self) -> bool;
|
||||
fn usage_hint_audience(&self) -> UsageHintAudience;
|
||||
fn root_agent_usage_hint_text(&self) -> Option<&str>;
|
||||
fn subagent_usage_hint_text(&self) -> Option<&str>;
|
||||
}
|
||||
|
||||
/// Prompt-only extension that contributes one MultiAgentV2 usage hint when enabled.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct MultiAgentV2Extension;
|
||||
|
||||
impl MultiAgentV2Extension {
|
||||
/// Creates an extension instance.
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// Returns the usage hint text that should be shown for this agent, if any.
|
||||
pub fn usage_hint_text<'a, C: MultiAgentV2Context>(&self, context: &'a C) -> Option<&'a str> {
|
||||
if !context.multi_agent_v2_enabled() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match context.usage_hint_audience() {
|
||||
UsageHintAudience::RootAgent => context.root_agent_usage_hint_text(),
|
||||
UsageHintAudience::SpawnedSubagent => context.subagent_usage_hint_text(),
|
||||
UsageHintAudience::Omitted => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: MultiAgentV2Context> PromptContributor<C> for MultiAgentV2Extension {
|
||||
fn contribute(&self, context: &C) -> Vec<PromptFragment> {
|
||||
self.usage_hint_text(context)
|
||||
.map(PromptFragment::separate_developer)
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: MultiAgentV2Context> CodexExtension<C> for MultiAgentV2Extension {
|
||||
fn install(self: Arc<Self>, registry: &mut ExtensionRegistryBuilder<C>) {
|
||||
registry.prompt_contributor(self);
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a shared MultiAgentV2 extension instance.
|
||||
pub fn extension() -> Arc<MultiAgentV2Extension> {
|
||||
Arc::new(MultiAgentV2Extension::new())
|
||||
}
|
||||
Reference in New Issue
Block a user