mirror of
https://github.com/openai/codex.git
synced 2026-04-09 23:34:50 +00:00
Compare commits
7 Commits
codex/func
...
codex-debu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c39477a7d5 | ||
|
|
cb77bbfed0 | ||
|
|
5f1363d6d0 | ||
|
|
8558e8aa51 | ||
|
|
22c1fc0131 | ||
|
|
2bbab7d8f9 | ||
|
|
d47b755aa2 |
@@ -1,6 +1,7 @@
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::PUBLIC_TOOL_NAME;
|
||||
|
||||
@@ -57,6 +58,12 @@ pub struct ToolDefinition {
|
||||
pub output_schema: Option<JsonValue>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ToolNamespaceDescription {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct CodeModeExecPragma {
|
||||
@@ -163,6 +170,7 @@ pub fn is_code_mode_nested_tool(tool_name: &str) -> bool {
|
||||
|
||||
pub fn build_exec_tool_description(
|
||||
enabled_tools: &[(String, String)],
|
||||
namespace_descriptions: &BTreeMap<String, ToolNamespaceDescription>,
|
||||
code_mode_only: bool,
|
||||
) -> String {
|
||||
if !code_mode_only {
|
||||
@@ -175,17 +183,38 @@ pub fn build_exec_tool_description(
|
||||
];
|
||||
|
||||
if !enabled_tools.is_empty() {
|
||||
let nested_tool_reference = enabled_tools
|
||||
.iter()
|
||||
.map(|(name, nested_description)| {
|
||||
let global_name = normalize_code_mode_identifier(name);
|
||||
format!(
|
||||
"### `{global_name}` (`{name}`)\n{}",
|
||||
nested_description.trim()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n\n");
|
||||
let mut current_namespace: Option<&str> = None;
|
||||
let mut nested_tool_sections = Vec::with_capacity(enabled_tools.len());
|
||||
|
||||
for (name, nested_description) in enabled_tools {
|
||||
let next_namespace = namespace_descriptions
|
||||
.get(name)
|
||||
.map(|namespace_description| namespace_description.name.as_str());
|
||||
if next_namespace != current_namespace {
|
||||
if let Some(namespace_description) = namespace_descriptions.get(name) {
|
||||
let namespace_description_text = namespace_description.description.trim();
|
||||
if !namespace_description_text.is_empty() {
|
||||
nested_tool_sections.push(format!(
|
||||
"## {}\n{namespace_description_text}",
|
||||
namespace_description.name
|
||||
));
|
||||
}
|
||||
}
|
||||
current_namespace = next_namespace;
|
||||
}
|
||||
|
||||
let global_name = normalize_code_mode_identifier(name);
|
||||
let nested_description = nested_description.trim();
|
||||
if nested_description.is_empty() {
|
||||
nested_tool_sections.push(format!("### `{global_name}` (`{name}`)"));
|
||||
} else {
|
||||
nested_tool_sections.push(format!(
|
||||
"### `{global_name}` (`{name}`)\n{nested_description}"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let nested_tool_reference = nested_tool_sections.join("\n\n");
|
||||
sections.push(nested_tool_reference);
|
||||
}
|
||||
|
||||
@@ -524,12 +553,14 @@ mod tests {
|
||||
use super::CodeModeToolKind;
|
||||
use super::ParsedExecSource;
|
||||
use super::ToolDefinition;
|
||||
use super::ToolNamespaceDescription;
|
||||
use super::augment_tool_definition;
|
||||
use super::build_exec_tool_description;
|
||||
use super::normalize_code_mode_identifier;
|
||||
use super::parse_exec_source;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[test]
|
||||
fn parse_exec_source_without_pragma() {
|
||||
@@ -646,6 +677,7 @@ mod tests {
|
||||
fn code_mode_only_description_includes_nested_tools() {
|
||||
let description = build_exec_tool_description(
|
||||
&[("foo".to_string(), "bar".to_string())],
|
||||
&BTreeMap::new(),
|
||||
/*code_mode_only*/ true,
|
||||
);
|
||||
assert!(description.contains("### `foo` (`foo`)"));
|
||||
@@ -653,8 +685,67 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn exec_description_mentions_timeout_helpers() {
|
||||
let description = build_exec_tool_description(&[], /*code_mode_only*/ false);
|
||||
let description =
|
||||
build_exec_tool_description(&[], &BTreeMap::new(), /*code_mode_only*/ false);
|
||||
assert!(description.contains("`setTimeout(callback: () => void, delayMs?: number)`"));
|
||||
assert!(description.contains("`clearTimeout(timeoutId?: number)`"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn code_mode_only_description_groups_namespace_instructions_once() {
|
||||
let namespace_descriptions = BTreeMap::from([
|
||||
(
|
||||
"mcp__sample__alpha".to_string(),
|
||||
ToolNamespaceDescription {
|
||||
name: "mcp__sample".to_string(),
|
||||
description: "Shared namespace guidance.".to_string(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"mcp__sample__beta".to_string(),
|
||||
ToolNamespaceDescription {
|
||||
name: "mcp__sample".to_string(),
|
||||
description: "Shared namespace guidance.".to_string(),
|
||||
},
|
||||
),
|
||||
]);
|
||||
let description = build_exec_tool_description(
|
||||
&[
|
||||
("mcp__sample__alpha".to_string(), "First tool".to_string()),
|
||||
("mcp__sample__beta".to_string(), "Second tool".to_string()),
|
||||
],
|
||||
&namespace_descriptions,
|
||||
/*code_mode_only*/ true,
|
||||
);
|
||||
assert_eq!(description.matches("## mcp__sample").count(), 1);
|
||||
assert!(description.contains(
|
||||
r#"## mcp__sample
|
||||
Shared namespace guidance.
|
||||
|
||||
### `mcp__sample__alpha` (`mcp__sample__alpha`)
|
||||
First tool
|
||||
|
||||
### `mcp__sample__beta` (`mcp__sample__beta`)
|
||||
Second tool"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn code_mode_only_description_omits_empty_namespace_sections() {
|
||||
let namespace_descriptions = BTreeMap::from([(
|
||||
"mcp__sample__alpha".to_string(),
|
||||
ToolNamespaceDescription {
|
||||
name: "mcp__sample".to_string(),
|
||||
description: String::new(),
|
||||
},
|
||||
)]);
|
||||
let description = build_exec_tool_description(
|
||||
&[("mcp__sample__alpha".to_string(), "First tool".to_string())],
|
||||
&namespace_descriptions,
|
||||
/*code_mode_only*/ true,
|
||||
);
|
||||
|
||||
assert!(!description.contains("## mcp__sample"));
|
||||
assert!(description.contains("### `mcp__sample__alpha` (`mcp__sample__alpha`)"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ mod service;
|
||||
pub use description::CODE_MODE_PRAGMA_PREFIX;
|
||||
pub use description::CodeModeToolKind;
|
||||
pub use description::ToolDefinition;
|
||||
pub use description::ToolNamespaceDescription;
|
||||
pub use description::append_code_mode_sample;
|
||||
pub use description::augment_tool_definition;
|
||||
pub use description::build_exec_tool_description;
|
||||
|
||||
@@ -186,6 +186,8 @@ pub struct ToolInfo {
|
||||
pub server_name: String,
|
||||
pub tool_name: String,
|
||||
pub tool_namespace: String,
|
||||
#[serde(default)]
|
||||
pub server_instructions: Option<String>,
|
||||
pub tool: Tool,
|
||||
pub connector_id: Option<String>,
|
||||
pub connector_name: Option<String>,
|
||||
@@ -356,6 +358,7 @@ struct ManagedClient {
|
||||
tools: Vec<ToolInfo>,
|
||||
tool_filter: ToolFilter,
|
||||
tool_timeout: Option<Duration>,
|
||||
server_instructions: Option<String>,
|
||||
server_supports_sandbox_state_capability: bool,
|
||||
codex_apps_tools_cache_context: Option<CodexAppsToolsCacheContext>,
|
||||
}
|
||||
@@ -842,6 +845,7 @@ impl McpConnectionManager {
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
&managed_client.client,
|
||||
managed_client.tool_timeout,
|
||||
managed_client.server_instructions.as_deref(),
|
||||
)
|
||||
.await
|
||||
.with_context(|| {
|
||||
@@ -1374,9 +1378,14 @@ async fn start_server_task(
|
||||
|
||||
let list_start = Instant::now();
|
||||
let fetch_start = Instant::now();
|
||||
let tools = list_tools_for_client_uncached(&server_name, &client, startup_timeout)
|
||||
.await
|
||||
.map_err(StartupOutcomeError::from)?;
|
||||
let tools = list_tools_for_client_uncached(
|
||||
&server_name,
|
||||
&client,
|
||||
startup_timeout,
|
||||
initialize_result.instructions.as_deref(),
|
||||
)
|
||||
.await
|
||||
.map_err(StartupOutcomeError::from)?;
|
||||
emit_duration(
|
||||
MCP_TOOLS_FETCH_UNCACHED_DURATION_METRIC,
|
||||
fetch_start.elapsed(),
|
||||
@@ -1407,6 +1416,7 @@ async fn start_server_task(
|
||||
tools,
|
||||
tool_timeout: Some(tool_timeout),
|
||||
tool_filter,
|
||||
server_instructions: initialize_result.instructions,
|
||||
server_supports_sandbox_state_capability,
|
||||
codex_apps_tools_cache_context,
|
||||
};
|
||||
@@ -1587,6 +1597,7 @@ async fn list_tools_for_client_uncached(
|
||||
server_name: &str,
|
||||
client: &Arc<RmcpClient>,
|
||||
timeout: Option<Duration>,
|
||||
server_instructions: Option<&str>,
|
||||
) -> Result<Vec<ToolInfo>> {
|
||||
let resp = client
|
||||
.list_tools_with_connector_ids(/*params*/ None, timeout)
|
||||
@@ -1617,6 +1628,7 @@ async fn list_tools_for_client_uncached(
|
||||
server_name: server_name.to_owned(),
|
||||
tool_name,
|
||||
tool_namespace,
|
||||
server_instructions: server_instructions.map(str::to_string),
|
||||
tool: tool_def,
|
||||
connector_id: tool.connector_id,
|
||||
connector_name,
|
||||
|
||||
@@ -15,6 +15,7 @@ fn create_test_tool(server_name: &str, tool_name: &str) -> ToolInfo {
|
||||
} else {
|
||||
server_name.to_string()
|
||||
},
|
||||
server_instructions: None,
|
||||
tool: Tool {
|
||||
name: tool_name.to_string().into(),
|
||||
title: None,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use codex_execpolicy::Decision;
|
||||
use codex_execpolicy::PatternToken;
|
||||
use codex_execpolicy::Policy;
|
||||
use codex_execpolicy::PrefixPattern;
|
||||
use codex_execpolicy::PrefixRule;
|
||||
use codex_execpolicy::RuleRef;
|
||||
use codex_execpolicy::rule::PatternToken;
|
||||
use codex_execpolicy::rule::PrefixPattern;
|
||||
use codex_execpolicy::rule::PrefixRule;
|
||||
use multimap::MultiMap;
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -6888,16 +6888,18 @@ pub(crate) async fn built_tools(
|
||||
} else {
|
||||
app_tools
|
||||
};
|
||||
let mcp_tool_router_inputs =
|
||||
has_mcp_servers.then(|| crate::tools::router::map_mcp_tool_infos(&mcp_tools));
|
||||
|
||||
Ok(Arc::new(ToolRouter::from_config(
|
||||
&turn_context.tools_config,
|
||||
ToolRouterParams {
|
||||
mcp_tools: has_mcp_servers.then(|| {
|
||||
mcp_tools
|
||||
.into_iter()
|
||||
.map(|(name, tool)| (name, tool.tool))
|
||||
.collect()
|
||||
}),
|
||||
mcp_tools: mcp_tool_router_inputs
|
||||
.as_ref()
|
||||
.map(|inputs| inputs.mcp_tools.clone()),
|
||||
tool_namespaces: mcp_tool_router_inputs
|
||||
.as_ref()
|
||||
.map(|inputs| inputs.tool_namespaces.clone()),
|
||||
app_tools,
|
||||
discoverable_tools,
|
||||
dynamic_tools: turn_context.dynamic_tools.as_slice(),
|
||||
|
||||
@@ -121,7 +121,7 @@ mod guardian_tests;
|
||||
|
||||
struct InstructionsTestCase {
|
||||
slug: &'static str,
|
||||
expects_apply_patch_instructions: bool,
|
||||
expects_apply_patch_description: bool,
|
||||
}
|
||||
|
||||
fn user_message(text: &str) -> ResponseItem {
|
||||
@@ -305,6 +305,7 @@ fn test_tool_runtime(session: Arc<Session>, turn_context: Arc<TurnContext>) -> T
|
||||
&turn_context.tools_config,
|
||||
crate::tools::router::ToolRouterParams {
|
||||
mcp_tools: None,
|
||||
tool_namespaces: None,
|
||||
app_tools: None,
|
||||
discoverable_tools: None,
|
||||
dynamic_tools: turn_context.dynamic_tools.as_slice(),
|
||||
@@ -413,6 +414,7 @@ fn make_mcp_tool(
|
||||
server_name: server_name.to_string(),
|
||||
tool_name: tool_name.to_string(),
|
||||
tool_namespace,
|
||||
server_instructions: None,
|
||||
tool: Tool {
|
||||
name: tool_name.to_string().into(),
|
||||
title: None,
|
||||
@@ -695,19 +697,19 @@ async fn get_base_instructions_no_user_content() {
|
||||
let test_cases = vec![
|
||||
InstructionsTestCase {
|
||||
slug: "gpt-5",
|
||||
expects_apply_patch_instructions: false,
|
||||
expects_apply_patch_description: false,
|
||||
},
|
||||
InstructionsTestCase {
|
||||
slug: "gpt-5.1",
|
||||
expects_apply_patch_instructions: false,
|
||||
expects_apply_patch_description: false,
|
||||
},
|
||||
InstructionsTestCase {
|
||||
slug: "gpt-5.1-codex",
|
||||
expects_apply_patch_instructions: false,
|
||||
expects_apply_patch_description: false,
|
||||
},
|
||||
InstructionsTestCase {
|
||||
slug: "gpt-5.1-codex-max",
|
||||
expects_apply_patch_instructions: false,
|
||||
expects_apply_patch_description: false,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -716,7 +718,7 @@ async fn get_base_instructions_no_user_content() {
|
||||
|
||||
for test_case in test_cases {
|
||||
let model_info = model_info_for_slug(test_case.slug, &config);
|
||||
if test_case.expects_apply_patch_instructions {
|
||||
if test_case.expects_apply_patch_description {
|
||||
assert_eq!(
|
||||
model_info.base_instructions.as_str(),
|
||||
prompt_with_apply_patch_instructions
|
||||
@@ -5292,15 +5294,12 @@ async fn fatal_tool_error_stops_turn_and_reports_error() {
|
||||
.await
|
||||
};
|
||||
let app_tools = Some(tools.clone());
|
||||
let mcp_tool_router_inputs = crate::tools::router::map_mcp_tool_infos(&tools);
|
||||
let router = ToolRouter::from_config(
|
||||
&turn_context.tools_config,
|
||||
crate::tools::router::ToolRouterParams {
|
||||
mcp_tools: Some(
|
||||
tools
|
||||
.into_iter()
|
||||
.map(|(name, tool)| (name, tool.tool))
|
||||
.collect(),
|
||||
),
|
||||
mcp_tools: Some(mcp_tool_router_inputs.mcp_tools),
|
||||
tool_namespaces: Some(mcp_tool_router_inputs.tool_namespaces),
|
||||
app_tools,
|
||||
discoverable_tools: None,
|
||||
dynamic_tools: turn_context.dynamic_tools.as_slice(),
|
||||
|
||||
@@ -112,6 +112,7 @@ fn codex_app_tool(
|
||||
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
tool_name: tool_name.to_string(),
|
||||
tool_namespace,
|
||||
server_instructions: None,
|
||||
tool: test_tool_definition(tool_name),
|
||||
connector_id: Some(connector_id.to_string()),
|
||||
connector_name: connector_name.map(ToOwned::to_owned),
|
||||
@@ -190,6 +191,7 @@ fn accessible_connectors_from_mcp_tools_carries_plugin_display_names() {
|
||||
server_name: "sample".to_string(),
|
||||
tool_name: "echo".to_string(),
|
||||
tool_namespace: "sample".to_string(),
|
||||
server_instructions: None,
|
||||
tool: test_tool_definition("echo"),
|
||||
connector_id: None,
|
||||
connector_name: None,
|
||||
@@ -314,6 +316,7 @@ fn accessible_connectors_from_mcp_tools_preserves_description() {
|
||||
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
tool_name: "calendar_create_event".to_string(),
|
||||
tool_namespace: "mcp__codex_apps__calendar".to_string(),
|
||||
server_instructions: None,
|
||||
tool: Tool {
|
||||
name: "calendar_create_event".to_string().into(),
|
||||
title: None,
|
||||
|
||||
@@ -257,15 +257,14 @@ async fn build_nested_router(exec: &ExecContext) -> ToolRouter {
|
||||
.read()
|
||||
.await
|
||||
.list_all_tools()
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|(name, tool_info)| (name, tool_info.tool))
|
||||
.collect();
|
||||
.await;
|
||||
let mcp_tool_router_inputs = crate::tools::router::map_mcp_tool_infos(&mcp_tools);
|
||||
|
||||
ToolRouter::from_config(
|
||||
&nested_tools_config,
|
||||
ToolRouterParams {
|
||||
mcp_tools: Some(mcp_tools),
|
||||
mcp_tools: Some(mcp_tool_router_inputs.mcp_tools),
|
||||
tool_namespaces: Some(mcp_tool_router_inputs.tool_namespaces),
|
||||
app_tools: None,
|
||||
discoverable_tools: None,
|
||||
dynamic_tools: exec.turn.dynamic_tools.as_slice(),
|
||||
|
||||
@@ -1561,16 +1561,13 @@ impl JsReplManager {
|
||||
.await
|
||||
.list_all_tools()
|
||||
.await;
|
||||
let mcp_tool_router_inputs = crate::tools::router::map_mcp_tool_infos(&mcp_tools);
|
||||
|
||||
let router = ToolRouter::from_config(
|
||||
&exec.turn.tools_config,
|
||||
crate::tools::router::ToolRouterParams {
|
||||
mcp_tools: Some(
|
||||
mcp_tools
|
||||
.into_iter()
|
||||
.map(|(name, tool)| (name, tool.tool))
|
||||
.collect(),
|
||||
),
|
||||
mcp_tools: Some(mcp_tool_router_inputs.mcp_tools),
|
||||
tool_namespaces: Some(mcp_tool_router_inputs.tool_namespaces),
|
||||
app_tools: None,
|
||||
discoverable_tools: None,
|
||||
dynamic_tools: exec.turn.dynamic_tools.as_slice(),
|
||||
|
||||
@@ -16,6 +16,7 @@ use codex_protocol::models::SearchToolCallParams;
|
||||
use codex_protocol::models::ShellToolCallParams;
|
||||
use codex_tools::ConfiguredToolSpec;
|
||||
use codex_tools::DiscoverableTool;
|
||||
use codex_tools::ToolNamespace;
|
||||
use codex_tools::ToolSpec;
|
||||
use codex_tools::ToolsConfig;
|
||||
use rmcp::model::Tool;
|
||||
@@ -41,15 +42,43 @@ pub struct ToolRouter {
|
||||
|
||||
pub(crate) struct ToolRouterParams<'a> {
|
||||
pub(crate) mcp_tools: Option<HashMap<String, Tool>>,
|
||||
pub(crate) tool_namespaces: Option<HashMap<String, ToolNamespace>>,
|
||||
pub(crate) app_tools: Option<HashMap<String, ToolInfo>>,
|
||||
pub(crate) discoverable_tools: Option<Vec<DiscoverableTool>>,
|
||||
pub(crate) dynamic_tools: &'a [DynamicToolSpec],
|
||||
}
|
||||
|
||||
pub(crate) struct McpToolRouterInputs {
|
||||
pub(crate) mcp_tools: HashMap<String, Tool>,
|
||||
pub(crate) tool_namespaces: HashMap<String, ToolNamespace>,
|
||||
}
|
||||
|
||||
pub(crate) fn map_mcp_tool_infos(mcp_tools: &HashMap<String, ToolInfo>) -> McpToolRouterInputs {
|
||||
McpToolRouterInputs {
|
||||
mcp_tools: mcp_tools
|
||||
.iter()
|
||||
.map(|(name, tool)| (name.clone(), tool.tool.clone()))
|
||||
.collect(),
|
||||
tool_namespaces: mcp_tools
|
||||
.iter()
|
||||
.map(|(name, tool)| {
|
||||
(
|
||||
name.clone(),
|
||||
ToolNamespace {
|
||||
name: tool.tool_namespace.clone(),
|
||||
description: tool.server_instructions.clone(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolRouter {
|
||||
pub fn from_config(config: &ToolsConfig, params: ToolRouterParams<'_>) -> Self {
|
||||
let ToolRouterParams {
|
||||
mcp_tools,
|
||||
tool_namespaces,
|
||||
app_tools,
|
||||
discoverable_tools,
|
||||
dynamic_tools,
|
||||
@@ -58,6 +87,7 @@ impl ToolRouter {
|
||||
config,
|
||||
mcp_tools,
|
||||
app_tools,
|
||||
tool_namespaces,
|
||||
discoverable_tools,
|
||||
dynamic_tools,
|
||||
);
|
||||
|
||||
@@ -35,6 +35,7 @@ async fn js_repl_tools_only_blocks_direct_tool_calls() -> anyhow::Result<()> {
|
||||
.map(|(name, tool)| (name, tool.tool))
|
||||
.collect(),
|
||||
),
|
||||
tool_namespaces: None,
|
||||
app_tools,
|
||||
discoverable_tools: None,
|
||||
dynamic_tools: turn.dynamic_tools.as_slice(),
|
||||
@@ -93,6 +94,7 @@ async fn js_repl_tools_only_allows_js_repl_source_calls() -> anyhow::Result<()>
|
||||
.map(|(name, tool)| (name, tool.tool))
|
||||
.collect(),
|
||||
),
|
||||
tool_namespaces: None,
|
||||
app_tools,
|
||||
discoverable_tools: None,
|
||||
dynamic_tools: turn.dynamic_tools.as_slice(),
|
||||
|
||||
@@ -10,6 +10,7 @@ use codex_mcp::ToolInfo;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_tools::DiscoverableTool;
|
||||
use codex_tools::ToolHandlerKind;
|
||||
use codex_tools::ToolNamespace;
|
||||
use codex_tools::ToolRegistryPlanAppTool;
|
||||
use codex_tools::ToolRegistryPlanParams;
|
||||
use codex_tools::ToolUserShellType;
|
||||
@@ -33,6 +34,7 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
config: &ToolsConfig,
|
||||
mcp_tools: Option<HashMap<String, rmcp::model::Tool>>,
|
||||
app_tools: Option<HashMap<String, ToolInfo>>,
|
||||
tool_namespaces: Option<HashMap<String, ToolNamespace>>,
|
||||
discoverable_tools: Option<Vec<DiscoverableTool>>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> ToolRegistryBuilder {
|
||||
@@ -86,6 +88,7 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
config,
|
||||
ToolRegistryPlanParams {
|
||||
mcp_tools: mcp_tools.as_ref(),
|
||||
tool_namespaces: tool_namespaces.as_ref(),
|
||||
app_tools: app_tool_sources.as_deref(),
|
||||
discoverable_tools: discoverable_tools.as_deref(),
|
||||
dynamic_tools,
|
||||
|
||||
@@ -181,6 +181,7 @@ fn build_specs(
|
||||
config,
|
||||
mcp_tools,
|
||||
app_tools,
|
||||
/*tool_namespaces*/ None,
|
||||
/*discoverable_tools*/ None,
|
||||
dynamic_tools,
|
||||
)
|
||||
@@ -261,6 +262,7 @@ fn assert_model_tools(
|
||||
&tools_config,
|
||||
ToolRouterParams {
|
||||
mcp_tools: None,
|
||||
tool_namespaces: None,
|
||||
app_tools: None,
|
||||
discoverable_tools: None,
|
||||
dynamic_tools: &[],
|
||||
@@ -628,6 +630,7 @@ fn tool_suggest_requires_apps_and_plugins_features() {
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*tool_namespaces*/ None,
|
||||
discoverable_tools.clone(),
|
||||
&[],
|
||||
)
|
||||
@@ -701,6 +704,7 @@ fn search_tool_description_falls_back_to_connector_name_without_description() {
|
||||
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
tool_name: "_create_event".to_string(),
|
||||
tool_namespace: "mcp__codex_apps__calendar".to_string(),
|
||||
server_instructions: None,
|
||||
tool: mcp_tool(
|
||||
"calendar_create_event",
|
||||
"Create calendar event",
|
||||
@@ -751,6 +755,7 @@ fn search_tool_registers_namespaced_app_tool_aliases() {
|
||||
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
tool_name: "_create_event".to_string(),
|
||||
tool_namespace: "mcp__codex_apps__calendar".to_string(),
|
||||
server_instructions: None,
|
||||
tool: mcp_tool(
|
||||
"calendar-create-event",
|
||||
"Create calendar event",
|
||||
@@ -768,6 +773,7 @@ fn search_tool_registers_namespaced_app_tool_aliases() {
|
||||
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
tool_name: "_list_events".to_string(),
|
||||
tool_namespace: "mcp__codex_apps__calendar".to_string(),
|
||||
server_instructions: None,
|
||||
tool: mcp_tool(
|
||||
"calendar-list-events",
|
||||
"List calendar events",
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
You are Codex, a coding agent based on GPT-5. You and the user share the same workspace and collaborate to achieve the user's goals.
|
||||
|
||||
# Personality
|
||||
You are a collaborative, highly capable pair-programmer AI. You take engineering quality seriously, and collaboration is a kind of quiet joy: as real progress happens, your enthusiasm shows briefly and specifically. Your default personality and tone is concise, direct, and friendly. You communicate efficiently, always keeping the user clearly informed about ongoing actions without unnecessary detail. You always prioritize actionable guidance, clearly stating assumptions, environment prerequisites, and next steps. Unless explicitly asked, you avoid excessively verbose explanations about your work.
|
||||
|
||||
## Tone and style
|
||||
- Anything you say outside of tool use is shown to the user. Do not narrate abstractly; explain what you are doing and why, using plain language.
|
||||
- Output will be rendered in a command line interface or minimal UI so keep responses tight, scannable, and low-noise. Generally avoid the use of emojis. You may format with GitHub-flavored Markdown.
|
||||
- Never use nested bullets. Keep lists flat (single level). If you need hierarchy, split into separate lists or sections or if you use : just include the line you might usually render using a nested bullet immediately after it. For numbered lists, only use the `1. 2. 3.` style markers (with a period), never `1)`.
|
||||
- When writing a final assistant response, state the solution first before explaining your answer. The complexity of the answer should match the task. If the task is simple, your answer should be short. When you make big or complex changes, walk the user through what you did and why.
|
||||
@@ -12,13 +5,6 @@ You are a collaborative, highly capable pair-programmer AI. You take engineering
|
||||
- Code samples or multi-line snippets should be wrapped in fenced code blocks. Include an info string as often as possible.
|
||||
- Never output the content of large files, just provide references. Use inline code to make file paths clickable; each reference should have a stand alone path, even if it's the same file. Paths may be absolute, workspace-relative, a//b/ diff-prefixed, or bare filename/suffix; locations may be :line[:column] or #Lline[Ccolumn] (1-based; column defaults to 1). Do not use file://, vscode://, or https://, and do not provide line ranges. Examples: src/app.ts, src/app.ts:42, b/server/index.js#L10, C:\repo\project\main.rs:12:5
|
||||
- The user does not see command execution outputs. When asked to show the output of a command (e.g. `git show`), relay the important details in your answer or summarize the key lines so the user understands the result.
|
||||
- Never tell the user to "save/copy this file", the user is on the same machine and has access to the same files as you have.
|
||||
- If you weren't able to do something, for example run tests, tell the user.
|
||||
- If there are natural next steps the user may want to take, suggest them at the end of your response. Do not make suggestions if there are no natural next steps.
|
||||
|
||||
## Responsiveness
|
||||
|
||||
### Collaboration posture:
|
||||
- If the user makes a simple request (such as asking for the time) which you can fulfill by running a terminal command (such as `date`), you should do so.
|
||||
- Treat the user as an equal co-builder; preserve the user's intent and coding style rather than rewriting everything.
|
||||
- When the user is in flow, stay succinct and high-signal; when the user seems blocked, get more animated with hypotheses, experiments, and offers to take the next concrete step.
|
||||
@@ -26,13 +12,6 @@ You are a collaborative, highly capable pair-programmer AI. You take engineering
|
||||
- Reference the collaboration explicitly when appropriate emphasizing shared achievement.
|
||||
|
||||
### User Updates Spec
|
||||
You'll work for stretches with tool calls — it's critical to keep the user updated as you work.
|
||||
|
||||
Tone:
|
||||
- Friendly, confident, senior-engineer energy. Positive, collaborative, humble; fix mistakes quickly.
|
||||
|
||||
Frequency & Length:
|
||||
- Send short updates (1–2 sentences) whenever there is a meaningful, important insight you need to share with the user to keep them informed.
|
||||
- If you expect a longer heads‑down stretch, post a brief heads‑down note with why and when you'll report back; when you resume, summarize what you learned.
|
||||
- Only the initial plan, plan updates, and final recap can be longer, with multiple bullets and paragraphs
|
||||
|
||||
@@ -40,13 +19,6 @@ Content:
|
||||
- Before you begin, give a quick plan with goal, constraints, next steps.
|
||||
- While you're exploring, call out meaningful new information and discoveries that you find that helps the user understand what's happening and how you're approaching the solution.
|
||||
- If you change the plan (e.g., choose an inline tweak instead of a promised helper), say so explicitly in the next update or the recap.
|
||||
- Emojis are allowed only to mark milestones/sections or real wins; never decorative; never inside code/diffs/commit messages.
|
||||
|
||||
# Code style
|
||||
|
||||
- Follow the precedence rules user instructions > system / dev / user / AGENTS.md instructions > match local file conventions > instructions below.
|
||||
- Use language-appropriate best practices.
|
||||
- Optimize for clarity, readability, and maintainability.
|
||||
- Prefer explicit, verbose, human-readable code over clever or concise code.
|
||||
- Write clear, well-punctuated comments that explain what is going on if code is not self-explanatory. You should not add comments like "Assigns the value to the variable", but a brief comment might be useful ahead of a complex code block that the user would otherwise have to spend time parsing out. Usage of these comments should be rare.
|
||||
- Default to ASCII when editing or creating files. Only introduce non-ASCII or other Unicode characters when there is a clear justification and the file already uses them.
|
||||
@@ -54,13 +26,6 @@ Content:
|
||||
# Reviews
|
||||
|
||||
When the user asks for a review, you default to a code-review mindset. Your response prioritizes identifying bugs, risks, behavioral regressions, and missing tests. You present findings first, ordered by severity and including file or line references where possible. Open questions or assumptions follow. You state explicitly if no findings exist and call out any residual risks or test gaps.
|
||||
|
||||
# Your environment
|
||||
|
||||
## Using GIT
|
||||
|
||||
- You may be working in a dirty git worktree.
|
||||
* NEVER revert existing changes you did not make unless explicitly requested, since these changes were made by the user.
|
||||
* If asked to make a commit or code edits and there are unrelated changes to your work or changes that you didn't make in those files, don't revert those changes.
|
||||
* If the changes are in files you've touched recently, you should read carefully and understand how you can work with the changes rather than reverting them.
|
||||
* If the changes are in unrelated files, just ignore them and don't revert them.
|
||||
|
||||
@@ -5,7 +5,7 @@ pub(crate) mod execpolicycheck;
|
||||
mod executable_name;
|
||||
pub(crate) mod parser;
|
||||
pub(crate) mod policy;
|
||||
pub(crate) mod rule;
|
||||
pub mod rule;
|
||||
|
||||
pub use amend::AmendError;
|
||||
pub use amend::blocking_append_allow_prefix_rule;
|
||||
|
||||
@@ -96,6 +96,7 @@ pub fn create_wait_tool() -> ToolSpec {
|
||||
|
||||
pub fn create_code_mode_tool(
|
||||
enabled_tools: &[(String, String)],
|
||||
namespace_descriptions: &BTreeMap<String, codex_code_mode::ToolNamespaceDescription>,
|
||||
code_mode_only_enabled: bool,
|
||||
) -> ToolSpec {
|
||||
const CODE_MODE_FREEFORM_GRAMMAR: &str = r#"
|
||||
@@ -112,6 +113,7 @@ SOURCE: /[\s\S]+/
|
||||
name: codex_code_mode::PUBLIC_TOOL_NAME.to_string(),
|
||||
description: codex_code_mode::build_exec_tool_description(
|
||||
enabled_tools,
|
||||
namespace_descriptions,
|
||||
code_mode_only_enabled,
|
||||
),
|
||||
format: FreeformToolFormat {
|
||||
|
||||
@@ -20,10 +20,14 @@ fn augment_tool_spec_for_code_mode_augments_function_tools() {
|
||||
description: "Look up an order".to_string(),
|
||||
strict: false,
|
||||
defer_loading: Some(true),
|
||||
parameters: JsonSchema::object(BTreeMap::from([(
|
||||
parameters: JsonSchema::object(
|
||||
BTreeMap::from([(
|
||||
"order_id".to_string(),
|
||||
JsonSchema::string(/*description*/ None),
|
||||
)]), Some(vec!["order_id".to_string()]), Some(AdditionalProperties::Boolean(false))),
|
||||
)]),
|
||||
Some(vec!["order_id".to_string()]),
|
||||
Some(AdditionalProperties::Boolean(false))
|
||||
),
|
||||
output_schema: Some(json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -34,13 +38,23 @@ fn augment_tool_spec_for_code_mode_augments_function_tools() {
|
||||
})),
|
||||
ToolSpec::Function(ResponsesApiTool {
|
||||
name: "lookup_order".to_string(),
|
||||
description: "Look up an order\n\nexec tool declaration:\n```ts\ndeclare const tools: { lookup_order(args: { order_id: string; }): Promise<{ ok: boolean; }>; };\n```".to_string(),
|
||||
description: r#"Look up an order
|
||||
|
||||
exec tool declaration:
|
||||
```ts
|
||||
declare const tools: { lookup_order(args: { order_id: string; }): Promise<{ ok: boolean; }>; };
|
||||
```"#
|
||||
.to_string(),
|
||||
strict: false,
|
||||
defer_loading: Some(true),
|
||||
parameters: JsonSchema::object(BTreeMap::from([(
|
||||
parameters: JsonSchema::object(
|
||||
BTreeMap::from([(
|
||||
"order_id".to_string(),
|
||||
JsonSchema::string(/*description*/ None),
|
||||
)]), Some(vec!["order_id".to_string()]), Some(AdditionalProperties::Boolean(false))),
|
||||
)]),
|
||||
Some(vec!["order_id".to_string()]),
|
||||
Some(AdditionalProperties::Boolean(false))
|
||||
),
|
||||
output_schema: Some(json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -92,7 +106,13 @@ fn tool_spec_to_code_mode_tool_definition_returns_augmented_nested_tools() {
|
||||
tool_spec_to_code_mode_tool_definition(&spec),
|
||||
Some(codex_code_mode::ToolDefinition {
|
||||
name: "apply_patch".to_string(),
|
||||
description: "Apply a patch\n\nexec tool declaration:\n```ts\ndeclare const tools: { apply_patch(input: string): Promise<unknown>; };\n```".to_string(),
|
||||
description: r#"Apply a patch
|
||||
|
||||
exec tool declaration:
|
||||
```ts
|
||||
declare const tools: { apply_patch(input: string): Promise<unknown>; };
|
||||
```"#
|
||||
.to_string(),
|
||||
kind: codex_code_mode::CodeModeToolKind::Freeform,
|
||||
input_schema: None,
|
||||
output_schema: None,
|
||||
@@ -165,11 +185,16 @@ fn create_code_mode_tool_matches_expected_spec() {
|
||||
let enabled_tools = vec![("update_plan".to_string(), "Update the plan".to_string())];
|
||||
|
||||
assert_eq!(
|
||||
create_code_mode_tool(&enabled_tools, /*code_mode_only_enabled*/ true),
|
||||
create_code_mode_tool(
|
||||
&enabled_tools,
|
||||
&BTreeMap::new(),
|
||||
/*code_mode_only_enabled*/ true,
|
||||
),
|
||||
ToolSpec::Freeform(FreeformTool {
|
||||
name: codex_code_mode::PUBLIC_TOOL_NAME.to_string(),
|
||||
description: codex_code_mode::build_exec_tool_description(
|
||||
&enabled_tools,
|
||||
&BTreeMap::new(),
|
||||
/*code_mode_only*/ true
|
||||
),
|
||||
format: FreeformToolFormat {
|
||||
|
||||
@@ -114,6 +114,7 @@ pub use tool_discovery::filter_tool_suggest_discoverable_tools_for_client;
|
||||
pub use tool_registry_plan::build_tool_registry_plan;
|
||||
pub use tool_registry_plan_types::ToolHandlerKind;
|
||||
pub use tool_registry_plan_types::ToolHandlerSpec;
|
||||
pub use tool_registry_plan_types::ToolNamespace;
|
||||
pub use tool_registry_plan_types::ToolRegistryPlan;
|
||||
pub use tool_registry_plan_types::ToolRegistryPlanAppTool;
|
||||
pub use tool_registry_plan_types::ToolRegistryPlanParams;
|
||||
|
||||
@@ -61,6 +61,7 @@ use crate::tool_registry_plan_types::agent_type_description;
|
||||
use codex_protocol::openai_models::ApplyPatchToolType;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use rmcp::model::Tool as McpTool;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub fn build_tool_registry_plan(
|
||||
config: &ToolsConfig,
|
||||
@@ -70,6 +71,20 @@ pub fn build_tool_registry_plan(
|
||||
let exec_permission_approvals_enabled = config.exec_permission_approvals_enabled;
|
||||
|
||||
if config.code_mode_enabled {
|
||||
let namespace_descriptions = params
|
||||
.tool_namespaces
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|(name, detail)| {
|
||||
(
|
||||
name.clone(),
|
||||
codex_code_mode::ToolNamespaceDescription {
|
||||
name: detail.name.clone(),
|
||||
description: detail.description.clone().unwrap_or_default(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
let nested_config = config.for_code_mode_nested_tools();
|
||||
let nested_plan = build_tool_registry_plan(
|
||||
&nested_config,
|
||||
@@ -78,7 +93,7 @@ pub fn build_tool_registry_plan(
|
||||
..params
|
||||
},
|
||||
);
|
||||
let enabled_tools = collect_code_mode_tool_definitions(
|
||||
let mut enabled_tools = collect_code_mode_tool_definitions(
|
||||
nested_plan
|
||||
.specs
|
||||
.iter()
|
||||
@@ -87,8 +102,15 @@ pub fn build_tool_registry_plan(
|
||||
.into_iter()
|
||||
.map(|tool| (tool.name, tool.description))
|
||||
.collect::<Vec<_>>();
|
||||
enabled_tools.sort_by(|(left_name, _), (right_name, _)| {
|
||||
compare_code_mode_tool_names(left_name, right_name, &namespace_descriptions)
|
||||
});
|
||||
plan.push_spec(
|
||||
create_code_mode_tool(&enabled_tools, config.code_mode_only_enabled),
|
||||
create_code_mode_tool(
|
||||
&enabled_tools,
|
||||
&namespace_descriptions,
|
||||
config.code_mode_only_enabled,
|
||||
),
|
||||
/*supports_parallel_tool_calls*/ false,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
@@ -494,6 +516,41 @@ pub fn build_tool_registry_plan(
|
||||
plan
|
||||
}
|
||||
|
||||
fn compare_code_mode_tool_names(
|
||||
left_name: &str,
|
||||
right_name: &str,
|
||||
namespace_descriptions: &BTreeMap<String, codex_code_mode::ToolNamespaceDescription>,
|
||||
) -> std::cmp::Ordering {
|
||||
let left_namespace = code_mode_namespace_name(left_name, namespace_descriptions);
|
||||
let right_namespace = code_mode_namespace_name(right_name, namespace_descriptions);
|
||||
|
||||
left_namespace
|
||||
.cmp(&right_namespace)
|
||||
.then_with(|| {
|
||||
code_mode_function_name(left_name, left_namespace)
|
||||
.cmp(code_mode_function_name(right_name, right_namespace))
|
||||
})
|
||||
.then_with(|| left_name.cmp(right_name))
|
||||
}
|
||||
|
||||
fn code_mode_namespace_name<'a>(
|
||||
name: &str,
|
||||
namespace_descriptions: &'a BTreeMap<String, codex_code_mode::ToolNamespaceDescription>,
|
||||
) -> Option<&'a str> {
|
||||
namespace_descriptions
|
||||
.get(name)
|
||||
.map(|namespace_description| namespace_description.name.as_str())
|
||||
}
|
||||
|
||||
fn code_mode_function_name<'a>(name: &'a str, namespace: Option<&str>) -> &'a str {
|
||||
namespace
|
||||
.and_then(|namespace| {
|
||||
name.strip_prefix(namespace)
|
||||
.and_then(|suffix| suffix.strip_prefix("__"))
|
||||
})
|
||||
.unwrap_or(name)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tool_registry_plan_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::ResponsesApiTool;
|
||||
use crate::ResponsesApiWebSearchFilters;
|
||||
use crate::ResponsesApiWebSearchUserLocation;
|
||||
use crate::ToolHandlerSpec;
|
||||
use crate::ToolNamespace;
|
||||
use crate::ToolRegistryPlanAppTool;
|
||||
use crate::ToolsConfigParams;
|
||||
use crate::WaitAgentTimeoutOptions;
|
||||
@@ -1332,6 +1333,7 @@ fn tool_suggest_is_not_registered_without_feature_flag() {
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*tool_namespaces*/ None,
|
||||
Some(vec![discoverable_connector(
|
||||
"connector_2128aebfecb84f64a069897515042a44",
|
||||
"Google Calendar",
|
||||
@@ -1371,6 +1373,7 @@ fn tool_suggest_can_be_registered_without_search_tool() {
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*tool_namespaces*/ None,
|
||||
Some(vec![discoverable_connector(
|
||||
"connector_2128aebfecb84f64a069897515042a44",
|
||||
"Google Calendar",
|
||||
@@ -1438,6 +1441,7 @@ fn tool_suggest_description_lists_discoverable_tools() {
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*tool_namespaces*/ None,
|
||||
Some(discoverable_tools),
|
||||
&[],
|
||||
);
|
||||
@@ -1501,6 +1505,7 @@ fn code_mode_augments_mcp_tool_descriptions_with_namespaced_sample() {
|
||||
let model_info = model_info();
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CodeMode);
|
||||
features.enable(Feature::CodeModeOnly);
|
||||
features.enable(Feature::UnifiedExec);
|
||||
let available_models = Vec::new();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
@@ -1542,7 +1547,12 @@ fn code_mode_augments_mcp_tool_descriptions_with_namespaced_sample() {
|
||||
|
||||
assert_eq!(
|
||||
description,
|
||||
"Echo text\n\nexec tool declaration:\n```ts\ndeclare const tools: { mcp__sample__echo(args: { message: string; }): Promise<{ _meta?: unknown; content: Array<unknown>; isError?: boolean; structuredContent?: unknown; }>; };\n```"
|
||||
r#"Echo text
|
||||
|
||||
exec tool declaration:
|
||||
```ts
|
||||
declare const tools: { mcp__sample__echo(args: { message: string; }): Promise<{ _meta?: unknown; content: Array<unknown>; isError?: boolean; structuredContent?: unknown; }>; };
|
||||
```"#
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1789,6 +1799,7 @@ fn build_specs<'a>(
|
||||
config,
|
||||
mcp_tools,
|
||||
app_tools,
|
||||
/*tool_namespaces*/ None,
|
||||
/*discoverable_tools*/ None,
|
||||
dynamic_tools,
|
||||
)
|
||||
@@ -1798,6 +1809,25 @@ fn build_specs_with_discoverable_tools<'a>(
|
||||
config: &ToolsConfig,
|
||||
mcp_tools: Option<HashMap<String, rmcp::model::Tool>>,
|
||||
app_tools: Option<Vec<ToolRegistryPlanAppTool<'a>>>,
|
||||
tool_namespaces: Option<HashMap<String, ToolNamespace>>,
|
||||
discoverable_tools: Option<Vec<DiscoverableTool>>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> (Vec<ConfiguredToolSpec>, Vec<ToolHandlerSpec>) {
|
||||
build_specs_with_optional_tool_namespaces(
|
||||
config,
|
||||
mcp_tools,
|
||||
tool_namespaces,
|
||||
app_tools,
|
||||
discoverable_tools,
|
||||
dynamic_tools,
|
||||
)
|
||||
}
|
||||
|
||||
fn build_specs_with_optional_tool_namespaces<'a>(
|
||||
config: &ToolsConfig,
|
||||
mcp_tools: Option<HashMap<String, rmcp::model::Tool>>,
|
||||
tool_namespaces: Option<HashMap<String, ToolNamespace>>,
|
||||
app_tools: Option<Vec<ToolRegistryPlanAppTool<'a>>>,
|
||||
discoverable_tools: Option<Vec<DiscoverableTool>>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> (Vec<ConfiguredToolSpec>, Vec<ToolHandlerSpec>) {
|
||||
@@ -1805,6 +1835,7 @@ fn build_specs_with_discoverable_tools<'a>(
|
||||
config,
|
||||
ToolRegistryPlanParams {
|
||||
mcp_tools: mcp_tools.as_ref(),
|
||||
tool_namespaces: tool_namespaces.as_ref(),
|
||||
app_tools: app_tools.as_deref(),
|
||||
discoverable_tools: discoverable_tools.as_deref(),
|
||||
dynamic_tools,
|
||||
|
||||
@@ -58,6 +58,7 @@ pub struct ToolRegistryPlan {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ToolRegistryPlanParams<'a> {
|
||||
pub mcp_tools: Option<&'a HashMap<String, McpTool>>,
|
||||
pub tool_namespaces: Option<&'a HashMap<String, ToolNamespace>>,
|
||||
pub app_tools: Option<&'a [ToolRegistryPlanAppTool<'a>]>,
|
||||
pub discoverable_tools: Option<&'a [DiscoverableTool]>,
|
||||
pub dynamic_tools: &'a [DynamicToolSpec],
|
||||
@@ -66,6 +67,12 @@ pub struct ToolRegistryPlanParams<'a> {
|
||||
pub codex_apps_mcp_server_name: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ToolNamespace {
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ToolRegistryPlanAppTool<'a> {
|
||||
pub tool_name: &'a str,
|
||||
|
||||
@@ -455,7 +455,10 @@ impl ChatComposer {
|
||||
realtime_conversation_enabled: false,
|
||||
audio_device_selection_enabled: false,
|
||||
windows_degraded_sandbox_active: false,
|
||||
is_zellij: codex_terminal_detection::terminal_info().is_zellij(),
|
||||
is_zellij: matches!(
|
||||
codex_terminal_detection::terminal_info().multiplexer,
|
||||
Some(codex_terminal_detection::Multiplexer::Zellij {})
|
||||
),
|
||||
status_line_value: None,
|
||||
status_line_enabled: false,
|
||||
active_agent_label: None,
|
||||
|
||||
@@ -14,6 +14,7 @@ use codex_app_server_client::InProcessAppServerClient;
|
||||
use codex_app_server_client::InProcessClientStartArgs;
|
||||
use codex_app_server_client::RemoteAppServerClient;
|
||||
use codex_app_server_client::RemoteAppServerConnectArgs;
|
||||
use codex_app_server_protocol::Account as AppServerAccount;
|
||||
use codex_app_server_protocol::AuthMode as AppServerAuthMode;
|
||||
use codex_app_server_protocol::ConfigWarningNotification;
|
||||
use codex_app_server_protocol::Thread as AppServerThread;
|
||||
@@ -1016,32 +1017,33 @@ async fn run_ratatui_app(
|
||||
// Initialize high-fidelity session event logging if enabled.
|
||||
session_log::maybe_init(&initial_config);
|
||||
|
||||
let mut app_server = Some(
|
||||
match start_app_server(
|
||||
&app_server_target,
|
||||
arg0_paths.clone(),
|
||||
initial_config.clone(),
|
||||
cli_kv_overrides.clone(),
|
||||
loader_overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
feedback.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(app_server) => AppServerSession::new(app_server)
|
||||
.with_remote_cwd_override(remote_cwd_override.clone()),
|
||||
Err(err) => {
|
||||
terminal_restore_guard.restore_silently();
|
||||
session_log::log_session_end();
|
||||
return Err(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let should_show_trust_screen_flag = !remote_mode && should_show_trust_screen(&initial_config);
|
||||
let mut trust_decision_was_made = false;
|
||||
let needs_onboarding_app_server =
|
||||
should_show_trust_screen_flag || initial_config.model_provider.requires_openai_auth;
|
||||
let mut onboarding_app_server = if needs_onboarding_app_server {
|
||||
Some(
|
||||
AppServerSession::new(
|
||||
start_app_server(
|
||||
&app_server_target,
|
||||
arg0_paths.clone(),
|
||||
initial_config.clone(),
|
||||
cli_kv_overrides.clone(),
|
||||
loader_overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
feedback.clone(),
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
.with_remote_cwd_override(remote_cwd_override.clone()),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let login_status = if initial_config.model_provider.requires_openai_auth {
|
||||
let Some(app_server) = onboarding_app_server.as_mut() else {
|
||||
unreachable!("onboarding app server should exist when auth is required");
|
||||
let Some(app_server) = app_server.as_mut() else {
|
||||
unreachable!("app server should exist when auth is required");
|
||||
};
|
||||
get_login_status(app_server, &initial_config).await?
|
||||
} else {
|
||||
@@ -1057,13 +1059,13 @@ async fn run_ratatui_app(
|
||||
show_login_screen,
|
||||
show_trust_screen: should_show_trust_screen_flag,
|
||||
login_status,
|
||||
app_server_request_handle: onboarding_app_server
|
||||
app_server_request_handle: app_server
|
||||
.as_ref()
|
||||
.map(AppServerSession::request_handle),
|
||||
config: initial_config.clone(),
|
||||
},
|
||||
if show_login_screen {
|
||||
onboarding_app_server.take()
|
||||
app_server.as_mut()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
@@ -1071,6 +1073,7 @@ async fn run_ratatui_app(
|
||||
)
|
||||
.await?;
|
||||
if onboarding_result.should_exit {
|
||||
shutdown_app_server_if_present(app_server.take()).await;
|
||||
terminal_restore_guard.restore_silently();
|
||||
session_log::log_session_end();
|
||||
let _ = tui.terminal.clear();
|
||||
@@ -1110,10 +1113,8 @@ async fn run_ratatui_app(
|
||||
initial_config
|
||||
}
|
||||
} else {
|
||||
shutdown_app_server_if_present(onboarding_app_server.take()).await;
|
||||
initial_config
|
||||
};
|
||||
shutdown_app_server_if_present(onboarding_app_server.take()).await;
|
||||
|
||||
let mut missing_session_exit = |id_str: &str, action: &str| {
|
||||
error!("Error finding conversation path: {id_str}");
|
||||
@@ -1131,42 +1132,16 @@ async fn run_ratatui_app(
|
||||
})
|
||||
};
|
||||
|
||||
let needs_app_server_session_lookup = cli.resume_last
|
||||
|| cli.fork_last
|
||||
|| cli.resume_session_id.is_some()
|
||||
|| cli.fork_session_id.is_some()
|
||||
|| cli.resume_picker
|
||||
|| cli.fork_picker;
|
||||
let mut session_lookup_app_server = if needs_app_server_session_lookup {
|
||||
Some(
|
||||
AppServerSession::new(
|
||||
start_app_server(
|
||||
&app_server_target,
|
||||
arg0_paths.clone(),
|
||||
config.clone(),
|
||||
cli_kv_overrides.clone(),
|
||||
loader_overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
feedback.clone(),
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
.with_remote_cwd_override(remote_cwd_override.clone()),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let use_fork = cli.fork_picker || cli.fork_last || cli.fork_session_id.is_some();
|
||||
let session_selection = if use_fork {
|
||||
if let Some(id_str) = cli.fork_session_id.as_deref() {
|
||||
let Some(app_server) = session_lookup_app_server.as_mut() else {
|
||||
unreachable!("session lookup app server should be initialized for --fork <id>");
|
||||
let Some(startup_app_server) = app_server.as_mut() else {
|
||||
unreachable!("app server should be initialized for --fork <id>");
|
||||
};
|
||||
match lookup_session_target_with_app_server(app_server, id_str).await? {
|
||||
match lookup_session_target_with_app_server(startup_app_server, id_str).await? {
|
||||
Some(target_session) => resume_picker::SessionSelection::Fork(target_session),
|
||||
None => {
|
||||
shutdown_app_server_if_present(session_lookup_app_server.take()).await;
|
||||
shutdown_app_server_if_present(app_server.take()).await;
|
||||
return missing_session_exit(id_str, "fork");
|
||||
}
|
||||
}
|
||||
@@ -1181,8 +1156,8 @@ async fn run_ratatui_app(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let Some(app_server) = session_lookup_app_server.as_mut() else {
|
||||
unreachable!("session lookup app server should be initialized for --fork --last");
|
||||
let Some(app_server) = app_server.as_mut() else {
|
||||
unreachable!("app server should be initialized for --fork --last");
|
||||
};
|
||||
match lookup_latest_session_target_with_app_server(
|
||||
app_server, &config, filter_cwd, /*include_non_interactive*/ false,
|
||||
@@ -1193,8 +1168,8 @@ async fn run_ratatui_app(
|
||||
None => resume_picker::SessionSelection::StartFresh,
|
||||
}
|
||||
} else if cli.fork_picker {
|
||||
let Some(app_server) = session_lookup_app_server.take() else {
|
||||
unreachable!("session lookup app server should be initialized for --fork picker");
|
||||
let Some(app_server) = app_server.take() else {
|
||||
unreachable!("app server should be initialized for --fork picker");
|
||||
};
|
||||
match resume_picker::run_fork_picker_with_app_server(
|
||||
&mut tui,
|
||||
@@ -1221,13 +1196,13 @@ async fn run_ratatui_app(
|
||||
resume_picker::SessionSelection::StartFresh
|
||||
}
|
||||
} else if let Some(id_str) = cli.resume_session_id.as_deref() {
|
||||
let Some(app_server) = session_lookup_app_server.as_mut() else {
|
||||
unreachable!("session lookup app server should be initialized for --resume <id>");
|
||||
let Some(startup_app_server) = app_server.as_mut() else {
|
||||
unreachable!("app server should be initialized for --resume <id>");
|
||||
};
|
||||
match lookup_session_target_with_app_server(app_server, id_str).await? {
|
||||
match lookup_session_target_with_app_server(startup_app_server, id_str).await? {
|
||||
Some(target_session) => resume_picker::SessionSelection::Resume(target_session),
|
||||
None => {
|
||||
shutdown_app_server_if_present(session_lookup_app_server.take()).await;
|
||||
shutdown_app_server_if_present(app_server.take()).await;
|
||||
return missing_session_exit(id_str, "resume");
|
||||
}
|
||||
}
|
||||
@@ -1238,8 +1213,8 @@ async fn run_ratatui_app(
|
||||
&config,
|
||||
cli.resume_show_all,
|
||||
);
|
||||
let Some(app_server) = session_lookup_app_server.as_mut() else {
|
||||
unreachable!("session lookup app server should be initialized for --resume --last");
|
||||
let Some(app_server) = app_server.as_mut() else {
|
||||
unreachable!("app server should be initialized for --resume --last");
|
||||
};
|
||||
match lookup_latest_session_target_with_app_server(
|
||||
app_server,
|
||||
@@ -1253,8 +1228,8 @@ async fn run_ratatui_app(
|
||||
None => resume_picker::SessionSelection::StartFresh,
|
||||
}
|
||||
} else if cli.resume_picker {
|
||||
let Some(app_server) = session_lookup_app_server.take() else {
|
||||
unreachable!("session lookup app server should be initialized for --resume picker");
|
||||
let Some(app_server) = app_server.take() else {
|
||||
unreachable!("app server should be initialized for --resume picker");
|
||||
};
|
||||
match resume_picker::run_resume_picker_with_app_server(
|
||||
&mut tui,
|
||||
@@ -1281,7 +1256,6 @@ async fn run_ratatui_app(
|
||||
} else {
|
||||
resume_picker::SessionSelection::StartFresh
|
||||
};
|
||||
shutdown_app_server_if_present(session_lookup_app_server.take()).await;
|
||||
|
||||
let current_cwd = config.cwd.clone();
|
||||
let allow_prompt = !remote_mode && cli.cwd.is_none();
|
||||
@@ -1367,28 +1341,32 @@ async fn run_ratatui_app(
|
||||
|
||||
let use_alt_screen = determine_alt_screen_mode(no_alt_screen, config.tui_alternate_screen);
|
||||
tui.set_alt_screen_enabled(use_alt_screen);
|
||||
let app_server = match start_app_server(
|
||||
&app_server_target,
|
||||
arg0_paths,
|
||||
config.clone(),
|
||||
cli_kv_overrides.clone(),
|
||||
loader_overrides,
|
||||
cloud_requirements.clone(),
|
||||
feedback.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(app_server) => app_server,
|
||||
Err(err) => {
|
||||
terminal_restore_guard.restore_silently();
|
||||
session_log::log_session_end();
|
||||
return Err(err);
|
||||
}
|
||||
let app_server = match app_server {
|
||||
Some(app_server) => app_server,
|
||||
None => match start_app_server(
|
||||
&app_server_target,
|
||||
arg0_paths,
|
||||
config.clone(),
|
||||
cli_kv_overrides.clone(),
|
||||
loader_overrides,
|
||||
cloud_requirements.clone(),
|
||||
feedback.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(app_server) => AppServerSession::new(app_server)
|
||||
.with_remote_cwd_override(remote_cwd_override.clone()),
|
||||
Err(err) => {
|
||||
terminal_restore_guard.restore_silently();
|
||||
session_log::log_session_end();
|
||||
return Err(err);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let app_result = App::run(
|
||||
&mut tui,
|
||||
AppServerSession::new(app_server).with_remote_cwd_override(remote_cwd_override),
|
||||
app_server,
|
||||
config,
|
||||
cli_kv_overrides.clone(),
|
||||
overrides.clone(),
|
||||
@@ -1603,7 +1581,10 @@ fn determine_alt_screen_mode(no_alt_screen: bool, tui_alternate_screen: AltScree
|
||||
AltScreenMode::Never => false,
|
||||
AltScreenMode::Auto => {
|
||||
let terminal_info = terminal_info();
|
||||
!terminal_info.is_zellij()
|
||||
!matches!(
|
||||
terminal_info.multiplexer,
|
||||
Some(codex_terminal_detection::Multiplexer::Zellij {})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1628,12 +1609,8 @@ async fn get_login_status(
|
||||
|
||||
let account = app_server.read_account().await?;
|
||||
Ok(match account.account {
|
||||
Some(codex_app_server_protocol::Account::ApiKey {}) => {
|
||||
LoginStatus::AuthMode(AppServerAuthMode::ApiKey)
|
||||
}
|
||||
Some(codex_app_server_protocol::Account::Chatgpt { .. }) => {
|
||||
LoginStatus::AuthMode(AppServerAuthMode::Chatgpt)
|
||||
}
|
||||
Some(AppServerAccount::ApiKey {}) => LoginStatus::AuthMode(AppServerAuthMode::ApiKey),
|
||||
Some(AppServerAccount::Chatgpt { .. }) => LoginStatus::AuthMode(AppServerAuthMode::Chatgpt),
|
||||
None => LoginStatus::NotAuthenticated,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -447,7 +447,7 @@ impl WidgetRef for Step {
|
||||
|
||||
pub(crate) async fn run_onboarding_app(
|
||||
args: OnboardingScreenArgs,
|
||||
mut app_server: Option<AppServerSession>,
|
||||
mut app_server: Option<&mut AppServerSession>,
|
||||
tui: &mut Tui,
|
||||
) -> Result<OnboardingResult> {
|
||||
use tokio_stream::StreamExt;
|
||||
@@ -533,9 +533,6 @@ pub(crate) async fn run_onboarding_app(
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(app_server) = app_server {
|
||||
app_server.shutdown().await.ok();
|
||||
}
|
||||
Ok(OnboardingResult {
|
||||
directory_trust_decision: onboarding_screen.directory_trust_decision(),
|
||||
should_exit: onboarding_screen.should_exit(),
|
||||
|
||||
@@ -270,7 +270,10 @@ impl Tui {
|
||||
// Cache this to avoid contention with the event reader.
|
||||
supports_color::on_cached(supports_color::Stream::Stdout);
|
||||
let _ = crate::terminal_palette::default_colors();
|
||||
let is_zellij = codex_terminal_detection::terminal_info().is_zellij();
|
||||
let is_zellij = matches!(
|
||||
codex_terminal_detection::terminal_info().multiplexer,
|
||||
Some(codex_terminal_detection::Multiplexer::Zellij {})
|
||||
);
|
||||
|
||||
Self {
|
||||
frame_requester,
|
||||
|
||||
@@ -20,7 +20,7 @@ pub const MAX_WIDTH: u32 = 2048;
|
||||
/// Maximum height used when resizing images before uploading.
|
||||
pub const MAX_HEIGHT: u32 = 768;
|
||||
|
||||
pub(crate) mod error;
|
||||
pub mod error;
|
||||
|
||||
pub use crate::error::ImageProcessingError;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user