Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions codex-rs/core/src/plugins/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ pub(crate) fn render_explicit_plugin_instructions(
));
}

if !plugin.app_connector_ids.is_empty() {
lines.push(format!(
"- App IDs declared by this plugin, including templates: {}.",
plugin
.app_connector_ids
.iter()
.map(|connector_id| format!("`{}`", connector_id.0))
.collect::<Vec<_>>()
.join(", ")
));
}

if lines.len() == 1 {
return None;
}
Expand Down
23 changes: 23 additions & 0 deletions codex-rs/core/src/plugins/render_tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::*;
use codex_plugin::AppConnectorId;
use pretty_assertions::assert_eq;

#[test]
Expand All @@ -21,3 +22,25 @@ fn render_plugins_section_includes_descriptions_and_skill_naming_guidance() {

assert_eq!(rendered, expected);
}

#[test]
fn render_explicit_plugin_instructions_include_declared_app_ids() {
let rendered = render_explicit_plugin_instructions(
&PluginCapabilitySummary {
display_name: "sample".to_string(),
app_connector_ids: vec![
AppConnectorId("connector_calendar".to_string()),
AppConnectorId("templated_apps_Databricks".to_string()),
],
..PluginCapabilitySummary::default()
},
&[],
&[],
)
.expect("plugin app IDs should render");

assert_eq!(
rendered,
"Capabilities from the `sample` plugin:\n- App IDs declared by this plugin, including templates: `connector_calendar`, `templated_apps_Databricks`.\nUse these plugin-associated capabilities to help solve the task."
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub(crate) fn create_request_plugin_install_tool() -> ToolSpec {
]);

let description = format!(
"# Request plugin/connector install\n\nUse this tool only after `{LIST_AVAILABLE_PLUGINS_TO_INSTALL_TOOL_NAME}` returns a plugin or connector that exactly matches the user's explicit request.\n\nDo not use it for adjacent capabilities, broad recommendations, or tools that merely seem useful. Pass the returned `tool_type` through directly, and pass the returned `id` as `tool_id`.\n\nIMPORTANT: DO NOT call this tool in parallel with other tools."
"# Request plugin/connector install\n\nUse this tool only when either:\n- `{LIST_AVAILABLE_PLUGINS_TO_INSTALL_TOOL_NAME}` returns a plugin or connector that exactly matches the user's explicit request.\n- An active plugin's invocation guidance provides an exact concrete app ID for a connector required by the user's explicit request.\n\nDo not use it for adjacent capabilities, broad recommendations, or tools that merely seem useful. When using a list result, pass the returned `tool_type` through directly and pass the returned `id` as `tool_id`. When using an active plugin's invocation guidance, use `connector` as `tool_type` and pass the concrete app ID as `tool_id`. Do not pass template IDs such as `templated_apps_*`.\n\nIMPORTANT: DO NOT call this tool in parallel with other tools."
);

ToolSpec::Function(ResponsesApiTool {
Expand Down Expand Up @@ -65,8 +65,10 @@ mod tests {
fn create_request_plugin_install_tool_uses_expected_wire_shape() {
let expected_description = concat!(
"# Request plugin/connector install\n\n",
"Use this tool only after `list_available_plugins_to_install` returns a plugin or connector that exactly matches the user's explicit request.\n\n",
"Do not use it for adjacent capabilities, broad recommendations, or tools that merely seem useful. Pass the returned `tool_type` through directly, and pass the returned `id` as `tool_id`.\n\n",
"Use this tool only when either:\n",
"- `list_available_plugins_to_install` returns a plugin or connector that exactly matches the user's explicit request.\n",
"- An active plugin's invocation guidance provides an exact concrete app ID for a connector required by the user's explicit request.\n\n",
"Do not use it for adjacent capabilities, broad recommendations, or tools that merely seem useful. When using a list result, pass the returned `tool_type` through directly and pass the returned `id` as `tool_id`. When using an active plugin's invocation guidance, use `connector` as `tool_type` and pass the concrete app ID as `tool_id`. Do not pass template IDs such as `templated_apps_*`.\n\n",
"IMPORTANT: DO NOT call this tool in parallel with other tools.",
);

Expand Down
7 changes: 5 additions & 2 deletions codex-rs/core/src/tools/spec_plan_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ async fn install_suggestion_tools_stay_visible_without_tool_search() {
}

#[tokio::test]
async fn request_plugin_install_description_defers_inventory_to_list_tool() {
async fn request_plugin_install_description_accepts_list_or_active_plugin_guidance() {
let plan = probe_with(
|turn| {
set_features(
Expand Down Expand Up @@ -686,7 +686,10 @@ async fn request_plugin_install_description_defers_inventory_to_list_tool() {
panic!("expected request_plugin_install function spec");
};
assert!(request_description.contains(
"Use this tool only after `list_available_plugins_to_install` returns a plugin or connector that exactly matches the user's explicit request."
"- `list_available_plugins_to_install` returns a plugin or connector that exactly matches the user's explicit request."
));
assert!(request_description.contains(
"- An active plugin's invocation guidance provides an exact concrete app ID for a connector required by the user's explicit request."
));
assert!(!request_description.contains("github"));
}
Expand Down
Loading