mirror of
https://github.com/openai/codex.git
synced 2026-04-19 20:24:50 +00:00
Compare commits
11 Commits
pr17694
...
codex/reor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df2b213aaf | ||
|
|
d53e1aa6e7 | ||
|
|
88c2095fed | ||
|
|
8b05d1abeb | ||
|
|
fac2f41559 | ||
|
|
4bd0c33fee | ||
|
|
e34d5757eb | ||
|
|
8b23405bd0 | ||
|
|
6bf39eda60 | ||
|
|
aa865ee56f | ||
|
|
da4e3b21cf |
@@ -258,8 +258,10 @@ use crate::mentions::collect_explicit_app_ids;
|
||||
use crate::mentions::collect_explicit_plugin_mentions;
|
||||
use crate::mentions::collect_tool_mentions_from_messages;
|
||||
use crate::network_policy_decision::execpolicy_network_rule_amendment;
|
||||
use crate::plugins::PluginMentionInstructionsContext;
|
||||
use crate::plugins::PluginsManager;
|
||||
use crate::plugins::build_plugin_injections;
|
||||
use crate::plugins::build_plugin_mention_developer_sections;
|
||||
use crate::plugins::build_plugin_mention_instructions_context;
|
||||
use crate::plugins::render_plugins_section;
|
||||
use crate::project_doc::get_user_instructions;
|
||||
use crate::protocol::AgentMessageContentDeltaEvent;
|
||||
@@ -2570,10 +2572,14 @@ impl Session {
|
||||
.await
|
||||
}
|
||||
|
||||
/// `plugin_mention_instructions` carries the already-resolved turn-local plugin mention
|
||||
/// context from the current user input so this builder can render that guidance without
|
||||
/// re-listing MCP tools.
|
||||
async fn build_settings_update_items(
|
||||
&self,
|
||||
reference_context_item: Option<&TurnContextItem>,
|
||||
current_context: &TurnContext,
|
||||
plugin_mention_instructions: &PluginMentionInstructionsContext,
|
||||
) -> Vec<ResponseItem> {
|
||||
// TODO: Make context updates a pure diff of persisted previous/current TurnContextItem
|
||||
// state so replay/backtracking is deterministic. Runtime inputs that affect model-visible
|
||||
@@ -2592,6 +2598,7 @@ impl Session {
|
||||
shell.as_ref(),
|
||||
exec_policy.as_ref(),
|
||||
self.features.enabled(Feature::Personality),
|
||||
plugin_mention_instructions,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3431,9 +3438,14 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// `plugin_mention_instructions` is optional because compaction/rebuild callers do not have
|
||||
/// the raw current-turn user input needed to resolve explicit plugin mentions. When present,
|
||||
/// it carries the already-resolved plugin/tool/app context for turn-local plugin guidance in
|
||||
/// the canonical developer envelope.
|
||||
pub(crate) async fn build_initial_context(
|
||||
&self,
|
||||
turn_context: &TurnContext,
|
||||
plugin_mention_instructions: Option<&PluginMentionInstructionsContext>,
|
||||
) -> Vec<ResponseItem> {
|
||||
let mut developer_sections = Vec::<String>::with_capacity(8);
|
||||
let mut contextual_user_sections = Vec::<String>::with_capacity(2);
|
||||
@@ -3554,6 +3566,11 @@ impl Session {
|
||||
{
|
||||
developer_sections.push(plugin_section);
|
||||
}
|
||||
if let Some(plugin_mention_instructions) = plugin_mention_instructions {
|
||||
developer_sections.extend(build_plugin_mention_developer_sections(
|
||||
plugin_mention_instructions,
|
||||
));
|
||||
}
|
||||
if turn_context.features.enabled(Feature::CodexGitCommit)
|
||||
&& let Some(commit_message_instruction) = commit_message_trailer_instruction(
|
||||
turn_context.config.commit_attribution.as_deref(),
|
||||
@@ -3641,21 +3658,30 @@ impl Session {
|
||||
/// Mid-turn compaction is the other path that can re-establish that baseline when it
|
||||
/// reinjects full initial context into replacement history. Other non-regular tasks
|
||||
/// intentionally do not update the baseline.
|
||||
///
|
||||
/// `plugin_mention_instructions` carries the current turn's already-resolved explicit
|
||||
/// plugin-mention context so whichever canonical builder path runs can render that guidance
|
||||
/// exactly once without re-listing MCP tools.
|
||||
pub(crate) async fn record_context_updates_and_set_reference_context_item(
|
||||
&self,
|
||||
turn_context: &TurnContext,
|
||||
plugin_mention_instructions: &PluginMentionInstructionsContext,
|
||||
) {
|
||||
let reference_context_item = {
|
||||
let state = self.state.lock().await;
|
||||
state.reference_context_item()
|
||||
};
|
||||
let should_inject_full_context = reference_context_item.is_none();
|
||||
let context_items = if should_inject_full_context {
|
||||
self.build_initial_context(turn_context).await
|
||||
let context_items = if reference_context_item.is_none() {
|
||||
self.build_initial_context(turn_context, Some(plugin_mention_instructions))
|
||||
.await
|
||||
} else {
|
||||
// Steady-state path: append only context diffs to minimize token overhead.
|
||||
self.build_settings_update_items(reference_context_item.as_ref(), turn_context)
|
||||
.await
|
||||
self.build_settings_update_items(
|
||||
reference_context_item.as_ref(),
|
||||
turn_context,
|
||||
plugin_mention_instructions,
|
||||
)
|
||||
.await
|
||||
};
|
||||
let turn_context_item = turn_context.to_turn_context_item();
|
||||
if !context_items.is_empty() {
|
||||
@@ -5528,15 +5554,14 @@ pub(crate) async fn run_turn(
|
||||
|
||||
let skills_outcome = Some(turn_context.turn_skills.outcome.as_ref());
|
||||
|
||||
sess.record_context_updates_and_set_reference_context_item(turn_context.as_ref())
|
||||
.await;
|
||||
|
||||
let loaded_plugins = sess
|
||||
.services
|
||||
.plugins_manager
|
||||
.plugins_for_config(&turn_context.config);
|
||||
// Structured plugin:// mentions are resolved from the current session's
|
||||
// enabled plugins, then converted into turn-scoped guidance below.
|
||||
// enabled plugins here for telemetry/tool selection. The canonical context
|
||||
// builders then render explicit plugin guidance from the already-resolved
|
||||
// per-turn plugin context built below.
|
||||
let mentioned_plugins =
|
||||
collect_explicit_plugin_mentions(&input, loaded_plugins.capability_summaries());
|
||||
let mcp_tools = if turn_context.apps_enabled() || !mentioned_plugins.is_empty() {
|
||||
@@ -5622,12 +5647,21 @@ pub(crate) async fn run_turn(
|
||||
.await;
|
||||
}
|
||||
|
||||
let plugin_items =
|
||||
build_plugin_injections(&mentioned_plugins, &mcp_tools, &available_connectors);
|
||||
let mentioned_plugin_metadata = mentioned_plugins
|
||||
.iter()
|
||||
.filter_map(crate::plugins::PluginCapabilitySummary::telemetry_metadata)
|
||||
.collect::<Vec<_>>();
|
||||
let plugin_mention_instructions = build_plugin_mention_instructions_context(
|
||||
&mentioned_plugins,
|
||||
&mcp_tools,
|
||||
&available_connectors,
|
||||
);
|
||||
|
||||
sess.record_context_updates_and_set_reference_context_item(
|
||||
turn_context.as_ref(),
|
||||
&plugin_mention_instructions,
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut explicitly_enabled_connectors = collect_explicit_app_ids(&input);
|
||||
explicitly_enabled_connectors.extend(collect_explicit_app_ids_from_skill_items(
|
||||
@@ -5703,10 +5737,6 @@ pub(crate) async fn run_turn(
|
||||
sess.record_conversation_items(&turn_context, &skill_items)
|
||||
.await;
|
||||
}
|
||||
if !plugin_items.is_empty() {
|
||||
sess.record_conversation_items(&turn_context, &plugin_items)
|
||||
.await;
|
||||
}
|
||||
|
||||
let skills_outcome = Some(turn_context.turn_skills.outcome.as_ref());
|
||||
sess.maybe_start_ghost_snapshot(Arc::clone(&turn_context), cancellation_token.child_token())
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::exec::ExecToolCallOutput;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::mcp_connection_manager::ToolInfo;
|
||||
use crate::models_manager::model_info;
|
||||
use crate::plugins::PluginMentionInstructionsContext;
|
||||
use crate::shell::default_user_shell;
|
||||
use crate::tools::format_exec_output_str;
|
||||
|
||||
@@ -957,14 +958,24 @@ async fn resumed_history_injects_initial_context_on_first_context_update_only()
|
||||
assert_eq!(expected, history_before_seed.raw_items());
|
||||
|
||||
session
|
||||
.record_context_updates_and_set_reference_context_item(&turn_context)
|
||||
.record_context_updates_and_set_reference_context_item(
|
||||
&turn_context,
|
||||
&PluginMentionInstructionsContext::default(),
|
||||
)
|
||||
.await;
|
||||
expected.extend(session.build_initial_context(&turn_context).await);
|
||||
expected.extend(
|
||||
session
|
||||
.build_initial_context(&turn_context, /*plugin_mention_instructions*/ None)
|
||||
.await,
|
||||
);
|
||||
let history_after_seed = session.clone_history().await;
|
||||
assert_eq!(expected, history_after_seed.raw_items());
|
||||
|
||||
session
|
||||
.record_context_updates_and_set_reference_context_item(&turn_context)
|
||||
.record_context_updates_and_set_reference_context_item(
|
||||
&turn_context,
|
||||
&PluginMentionInstructionsContext::default(),
|
||||
)
|
||||
.await;
|
||||
let history_after_second_seed = session.clone_history().await;
|
||||
assert_eq!(
|
||||
@@ -1162,24 +1173,34 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result<
|
||||
.await?;
|
||||
wait_for_event(&initial.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||
// The parent rollout writer drains asynchronously after turn completion.
|
||||
// Wait until the persisted JSONL includes the source user turn before forking from it.
|
||||
// Wait until the persisted JSONL includes the completed source turn before
|
||||
// forking from it; otherwise `fork_thread(usize::MAX, ...)` correctly
|
||||
// treats the source rollout as mid-turn and drops that active suffix.
|
||||
let mut source_history_persisted = false;
|
||||
for _ in 0..100 {
|
||||
let history = RolloutRecorder::get_rollout_history(&rollout_path).await;
|
||||
source_history_persisted = history.ok().is_some_and(|history| {
|
||||
history.get_rollout_items().into_iter().any(|item| {
|
||||
let rollout_items = history.get_rollout_items();
|
||||
let source_turn_recorded = rollout_items.iter().any(|item| {
|
||||
matches!(
|
||||
item,
|
||||
RolloutItem::ResponseItem(ResponseItem::Message { role, content, .. })
|
||||
if role == "user"
|
||||
&& content.iter().any(|content_item| {
|
||||
matches!(
|
||||
content_item,
|
||||
ContentItem::InputText { text } if text == "fork seed"
|
||||
)
|
||||
})
|
||||
item,
|
||||
RolloutItem::ResponseItem(ResponseItem::Message { role, content, .. })
|
||||
if role == "user"
|
||||
&& content.iter().any(|content_item| {
|
||||
matches!(
|
||||
content_item,
|
||||
ContentItem::InputText { text } if text == "fork seed"
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
});
|
||||
let source_turn_completed = rollout_items.iter().any(|item| {
|
||||
matches!(
|
||||
item,
|
||||
RolloutItem::EventMsg(EventMsg::TurnComplete(_) | EventMsg::TurnAborted(_))
|
||||
)
|
||||
});
|
||||
source_turn_recorded && source_turn_completed
|
||||
});
|
||||
if source_history_persisted {
|
||||
break;
|
||||
@@ -1188,7 +1209,7 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result<
|
||||
}
|
||||
assert!(
|
||||
source_history_persisted,
|
||||
"source rollout should contain the completed pre-fork user turn before forking"
|
||||
"source rollout should contain the committed pre-fork turn before forking"
|
||||
);
|
||||
|
||||
let mut fork_config = initial.config.clone();
|
||||
@@ -1338,7 +1359,9 @@ async fn thread_rollback_drops_last_turn_from_history() {
|
||||
let (sess, tc, rx) = make_session_and_context_with_rx().await;
|
||||
let rollout_path = attach_rollout_recorder(&sess).await;
|
||||
|
||||
let initial_context = sess.build_initial_context(tc.as_ref()).await;
|
||||
let initial_context = sess
|
||||
.build_initial_context(tc.as_ref(), /*plugin_mention_instructions*/ None)
|
||||
.await;
|
||||
let turn_1 = vec![
|
||||
user_message("turn 1 user"),
|
||||
assistant_message("turn 1 assistant"),
|
||||
@@ -1402,7 +1425,9 @@ async fn thread_rollback_clears_history_when_num_turns_exceeds_existing_turns()
|
||||
let (sess, tc, rx) = make_session_and_context_with_rx().await;
|
||||
attach_rollout_recorder(&sess).await;
|
||||
|
||||
let initial_context = sess.build_initial_context(tc.as_ref()).await;
|
||||
let initial_context = sess
|
||||
.build_initial_context(tc.as_ref(), /*plugin_mention_instructions*/ None)
|
||||
.await;
|
||||
let turn_1 = vec![user_message("turn 1 user")];
|
||||
let mut full_history = Vec::new();
|
||||
full_history.extend(initial_context.clone());
|
||||
@@ -1428,7 +1453,9 @@ async fn thread_rollback_clears_history_when_num_turns_exceeds_existing_turns()
|
||||
async fn thread_rollback_fails_without_persisted_rollout_path() {
|
||||
let (sess, tc, rx) = make_session_and_context_with_rx().await;
|
||||
|
||||
let initial_context = sess.build_initial_context(tc.as_ref()).await;
|
||||
let initial_context = sess
|
||||
.build_initial_context(tc.as_ref(), /*plugin_mention_instructions*/ None)
|
||||
.await;
|
||||
sess.record_into_history(&initial_context, tc.as_ref())
|
||||
.await;
|
||||
|
||||
@@ -1745,7 +1772,9 @@ async fn thread_rollback_persists_marker_and_replays_cumulatively() {
|
||||
async fn thread_rollback_fails_when_turn_in_progress() {
|
||||
let (sess, tc, rx) = make_session_and_context_with_rx().await;
|
||||
|
||||
let initial_context = sess.build_initial_context(tc.as_ref()).await;
|
||||
let initial_context = sess
|
||||
.build_initial_context(tc.as_ref(), /*plugin_mention_instructions*/ None)
|
||||
.await;
|
||||
sess.record_into_history(&initial_context, tc.as_ref())
|
||||
.await;
|
||||
|
||||
@@ -1766,7 +1795,9 @@ async fn thread_rollback_fails_when_turn_in_progress() {
|
||||
async fn thread_rollback_fails_when_num_turns_is_zero() {
|
||||
let (sess, tc, rx) = make_session_and_context_with_rx().await;
|
||||
|
||||
let initial_context = sess.build_initial_context(tc.as_ref()).await;
|
||||
let initial_context = sess
|
||||
.build_initial_context(tc.as_ref(), /*plugin_mention_instructions*/ None)
|
||||
.await;
|
||||
sess.record_into_history(&initial_context, tc.as_ref())
|
||||
.await;
|
||||
|
||||
@@ -3699,7 +3730,11 @@ async fn build_settings_update_items_emits_environment_item_for_network_changes(
|
||||
|
||||
let reference_context_item = previous_context.to_turn_context_item();
|
||||
let update_items = session
|
||||
.build_settings_update_items(Some(&reference_context_item), ¤t_context)
|
||||
.build_settings_update_items(
|
||||
Some(&reference_context_item),
|
||||
¤t_context,
|
||||
&PluginMentionInstructionsContext::default(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let environment_update = update_items
|
||||
@@ -3734,7 +3769,11 @@ async fn build_settings_update_items_emits_environment_item_for_time_changes() {
|
||||
|
||||
let reference_context_item = previous_context.to_turn_context_item();
|
||||
let update_items = session
|
||||
.build_settings_update_items(Some(&reference_context_item), ¤t_context)
|
||||
.build_settings_update_items(
|
||||
Some(&reference_context_item),
|
||||
¤t_context,
|
||||
&PluginMentionInstructionsContext::default(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let environment_update = update_items
|
||||
@@ -3769,6 +3808,7 @@ async fn build_settings_update_items_emits_realtime_start_when_session_becomes_l
|
||||
.build_settings_update_items(
|
||||
Some(&previous_context.to_turn_context_item()),
|
||||
¤t_context,
|
||||
&PluginMentionInstructionsContext::default(),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -3797,6 +3837,7 @@ async fn build_settings_update_items_emits_realtime_end_when_session_stops_being
|
||||
.build_settings_update_items(
|
||||
Some(&previous_context.to_turn_context_item()),
|
||||
¤t_context,
|
||||
&PluginMentionInstructionsContext::default(),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -3830,7 +3871,11 @@ async fn build_settings_update_items_uses_previous_turn_settings_for_realtime_en
|
||||
.set_previous_turn_settings(Some(previous_turn_settings))
|
||||
.await;
|
||||
let update_items = session
|
||||
.build_settings_update_items(Some(&previous_context_item), ¤t_context)
|
||||
.build_settings_update_items(
|
||||
Some(&previous_context_item),
|
||||
¤t_context,
|
||||
&PluginMentionInstructionsContext::default(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let developer_texts = developer_input_texts(&update_items);
|
||||
@@ -3847,7 +3892,9 @@ async fn build_initial_context_uses_previous_realtime_state() {
|
||||
let (session, mut turn_context) = make_session_and_context().await;
|
||||
turn_context.realtime_active = true;
|
||||
|
||||
let initial_context = session.build_initial_context(&turn_context).await;
|
||||
let initial_context = session
|
||||
.build_initial_context(&turn_context, /*plugin_mention_instructions*/ None)
|
||||
.await;
|
||||
let developer_texts = developer_input_texts(&initial_context);
|
||||
assert!(
|
||||
developer_texts
|
||||
@@ -3861,7 +3908,9 @@ async fn build_initial_context_uses_previous_realtime_state() {
|
||||
let mut state = session.state.lock().await;
|
||||
state.set_reference_context_item(Some(previous_context_item));
|
||||
}
|
||||
let resumed_context = session.build_initial_context(&turn_context).await;
|
||||
let resumed_context = session
|
||||
.build_initial_context(&turn_context, /*plugin_mention_instructions*/ None)
|
||||
.await;
|
||||
let resumed_developer_texts = developer_input_texts(&resumed_context);
|
||||
assert!(
|
||||
!resumed_developer_texts
|
||||
@@ -3886,7 +3935,9 @@ async fn build_initial_context_omits_default_image_save_location_with_image_hist
|
||||
)
|
||||
.await;
|
||||
|
||||
let initial_context = session.build_initial_context(&turn_context).await;
|
||||
let initial_context = session
|
||||
.build_initial_context(&turn_context, /*plugin_mention_instructions*/ None)
|
||||
.await;
|
||||
let developer_texts = developer_input_texts(&initial_context);
|
||||
assert!(
|
||||
!developer_texts
|
||||
@@ -3900,7 +3951,9 @@ async fn build_initial_context_omits_default_image_save_location_with_image_hist
|
||||
async fn build_initial_context_omits_default_image_save_location_without_image_history() {
|
||||
let (session, turn_context) = make_session_and_context().await;
|
||||
|
||||
let initial_context = session.build_initial_context(&turn_context).await;
|
||||
let initial_context = session
|
||||
.build_initial_context(&turn_context, /*plugin_mention_instructions*/ None)
|
||||
.await;
|
||||
let developer_texts = developer_input_texts(&initial_context);
|
||||
|
||||
assert!(
|
||||
@@ -4013,7 +4066,9 @@ async fn build_initial_context_uses_previous_turn_settings_for_realtime_end() {
|
||||
session
|
||||
.set_previous_turn_settings(Some(previous_turn_settings))
|
||||
.await;
|
||||
let initial_context = session.build_initial_context(&turn_context).await;
|
||||
let initial_context = session
|
||||
.build_initial_context(&turn_context, /*plugin_mention_instructions*/ None)
|
||||
.await;
|
||||
let developer_texts = developer_input_texts(&initial_context);
|
||||
assert!(
|
||||
developer_texts
|
||||
@@ -4035,7 +4090,9 @@ async fn build_initial_context_restates_realtime_start_when_reference_context_is
|
||||
session
|
||||
.set_previous_turn_settings(Some(previous_turn_settings))
|
||||
.await;
|
||||
let initial_context = session.build_initial_context(&turn_context).await;
|
||||
let initial_context = session
|
||||
.build_initial_context(&turn_context, /*plugin_mention_instructions*/ None)
|
||||
.await;
|
||||
let developer_texts = developer_input_texts(&initial_context);
|
||||
assert!(
|
||||
developer_texts
|
||||
@@ -4050,10 +4107,15 @@ async fn record_context_updates_and_set_reference_context_item_injects_full_cont
|
||||
{
|
||||
let (session, turn_context) = make_session_and_context().await;
|
||||
session
|
||||
.record_context_updates_and_set_reference_context_item(&turn_context)
|
||||
.record_context_updates_and_set_reference_context_item(
|
||||
&turn_context,
|
||||
&PluginMentionInstructionsContext::default(),
|
||||
)
|
||||
.await;
|
||||
let history = session.clone_history().await;
|
||||
let initial_context = session.build_initial_context(&turn_context).await;
|
||||
let initial_context = session
|
||||
.build_initial_context(&turn_context, /*plugin_mention_instructions*/ None)
|
||||
.await;
|
||||
assert_eq!(history.raw_items().to_vec(), initial_context);
|
||||
|
||||
let current_context = session.reference_context_item().await;
|
||||
@@ -4081,7 +4143,10 @@ async fn record_context_updates_and_set_reference_context_item_reinjects_full_co
|
||||
.record_into_history(std::slice::from_ref(&compacted_summary), &turn_context)
|
||||
.await;
|
||||
session
|
||||
.record_context_updates_and_set_reference_context_item(&turn_context)
|
||||
.record_context_updates_and_set_reference_context_item(
|
||||
&turn_context,
|
||||
&PluginMentionInstructionsContext::default(),
|
||||
)
|
||||
.await;
|
||||
{
|
||||
let mut state = session.state.lock().await;
|
||||
@@ -4092,12 +4157,19 @@ async fn record_context_updates_and_set_reference_context_item_reinjects_full_co
|
||||
.await;
|
||||
|
||||
session
|
||||
.record_context_updates_and_set_reference_context_item(&turn_context)
|
||||
.record_context_updates_and_set_reference_context_item(
|
||||
&turn_context,
|
||||
&PluginMentionInstructionsContext::default(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let history = session.clone_history().await;
|
||||
let mut expected_history = vec![compacted_summary];
|
||||
expected_history.extend(session.build_initial_context(&turn_context).await);
|
||||
expected_history.extend(
|
||||
session
|
||||
.build_initial_context(&turn_context, /*plugin_mention_instructions*/ None)
|
||||
.await,
|
||||
);
|
||||
assert_eq!(history.raw_items().to_vec(), expected_history);
|
||||
}
|
||||
|
||||
@@ -4141,12 +4213,19 @@ async fn record_context_updates_and_set_reference_context_item_persists_baseline
|
||||
}
|
||||
|
||||
let update_items = session
|
||||
.build_settings_update_items(Some(&previous_context_item), &turn_context)
|
||||
.build_settings_update_items(
|
||||
Some(&previous_context_item),
|
||||
&turn_context,
|
||||
&PluginMentionInstructionsContext::default(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(update_items, Vec::new());
|
||||
|
||||
session
|
||||
.record_context_updates_and_set_reference_context_item(&turn_context)
|
||||
.record_context_updates_and_set_reference_context_item(
|
||||
&turn_context,
|
||||
&PluginMentionInstructionsContext::default(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
@@ -4191,7 +4270,9 @@ async fn build_initial_context_prepends_model_switch_message() {
|
||||
session
|
||||
.set_previous_turn_settings(Some(previous_turn_settings))
|
||||
.await;
|
||||
let initial_context = session.build_initial_context(&turn_context).await;
|
||||
let initial_context = session
|
||||
.build_initial_context(&turn_context, /*plugin_mention_instructions*/ None)
|
||||
.await;
|
||||
|
||||
let ResponseItem::Message { role, content, .. } = &initial_context[0] else {
|
||||
panic!("expected developer message");
|
||||
@@ -4259,7 +4340,10 @@ async fn record_context_updates_and_set_reference_context_item_persists_full_rei
|
||||
}))
|
||||
.await;
|
||||
session
|
||||
.record_context_updates_and_set_reference_context_item(&turn_context)
|
||||
.record_context_updates_and_set_reference_context_item(
|
||||
&turn_context,
|
||||
&PluginMentionInstructionsContext::default(),
|
||||
)
|
||||
.await;
|
||||
session.ensure_rollout_materialized().await;
|
||||
session.flush_rollout().await;
|
||||
@@ -4872,7 +4956,10 @@ async fn sample_rollout(
|
||||
// personality_spec) matches reconstruction.
|
||||
let reconstruction_turn = session.new_default_turn().await;
|
||||
let mut initial_context = session
|
||||
.build_initial_context(reconstruction_turn.as_ref())
|
||||
.build_initial_context(
|
||||
reconstruction_turn.as_ref(),
|
||||
/*plugin_mention_instructions*/ None,
|
||||
)
|
||||
.await;
|
||||
// Ensure personality_spec is present when Personality is enabled, so expected matches
|
||||
// what reconstruction produces (build_initial_context may omit it when baked into model).
|
||||
|
||||
@@ -200,7 +200,12 @@ async fn run_compact_task_inner(
|
||||
initial_context_injection,
|
||||
InitialContextInjection::BeforeLastUserMessage
|
||||
) {
|
||||
let initial_context = sess.build_initial_context(turn_context.as_ref()).await;
|
||||
let initial_context = sess
|
||||
.build_initial_context(
|
||||
turn_context.as_ref(),
|
||||
/*plugin_mention_instructions*/ None,
|
||||
)
|
||||
.await;
|
||||
new_history =
|
||||
insert_initial_context_before_last_real_user_or_summary(new_history, initial_context);
|
||||
}
|
||||
|
||||
@@ -178,7 +178,8 @@ pub(crate) async fn process_compacted_history(
|
||||
initial_context_injection,
|
||||
InitialContextInjection::BeforeLastUserMessage
|
||||
) {
|
||||
sess.build_initial_context(turn_context).await
|
||||
sess.build_initial_context(turn_context, /*plugin_mention_instructions*/ None)
|
||||
.await
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
@@ -9,7 +9,9 @@ async fn process_compacted_history_with_test_session(
|
||||
session
|
||||
.set_previous_turn_settings(previous_turn_settings.cloned())
|
||||
.await;
|
||||
let initial_context = session.build_initial_context(&turn_context).await;
|
||||
let initial_context = session
|
||||
.build_initial_context(&turn_context, /*plugin_mention_instructions*/ None)
|
||||
.await;
|
||||
let refreshed = crate::compact_remote::process_compacted_history(
|
||||
&session,
|
||||
&turn_context,
|
||||
|
||||
@@ -954,6 +954,44 @@ fn drop_last_n_user_turns_trims_context_updates_above_rolled_back_turn() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_last_n_user_turns_trims_explicit_plugin_guidance_above_rolled_back_turn() {
|
||||
let items = vec![
|
||||
assistant_msg("session prefix item"),
|
||||
user_input_text_msg("turn 1 user"),
|
||||
assistant_msg("turn 1 assistant"),
|
||||
developer_msg(
|
||||
"<plugin_mention_instructions>\nCapabilities from the `sample` plugin:\n- Apps from this plugin available in this session: `Google Calendar`.\nUse these plugin-associated capabilities to help solve the task.\n</plugin_mention_instructions>",
|
||||
),
|
||||
user_input_text_msg(
|
||||
"<environment_context><cwd>PRETURN_CONTEXT_DIFF_CWD</cwd></environment_context>",
|
||||
),
|
||||
user_input_text_msg("turn 2 user"),
|
||||
assistant_msg("turn 2 assistant"),
|
||||
];
|
||||
|
||||
let modalities = default_input_modalities();
|
||||
let mut history = create_history_with_items(items);
|
||||
let reference_context_item = reference_context_item();
|
||||
history.set_reference_context_item(Some(reference_context_item.clone()));
|
||||
history.drop_last_n_user_turns(1);
|
||||
|
||||
assert_eq!(
|
||||
history.clone().for_prompt(&modalities),
|
||||
vec![
|
||||
assistant_msg("session prefix item"),
|
||||
user_input_text_msg("turn 1 user"),
|
||||
assistant_msg("turn 1 assistant"),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_value(history.reference_context_item())
|
||||
.expect("serialize retained reference context item"),
|
||||
serde_json::to_value(Some(reference_context_item))
|
||||
.expect("serialize expected reference context item")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_last_n_user_turns_clears_reference_context_for_mixed_developer_context_bundles() {
|
||||
let items = vec![
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::codex::PreviousTurnSettings;
|
||||
use crate::codex::TurnContext;
|
||||
use crate::environment_context::EnvironmentContext;
|
||||
use crate::plugins::PluginMentionInstructionsContext;
|
||||
use crate::plugins::build_plugin_mention_developer_sections;
|
||||
use crate::shell::Shell;
|
||||
use codex_execpolicy::Policy;
|
||||
use codex_features::Feature;
|
||||
@@ -192,6 +194,7 @@ pub(crate) fn build_settings_update_items(
|
||||
shell: &Shell,
|
||||
exec_policy: &Policy,
|
||||
personality_feature_enabled: bool,
|
||||
plugin_mention_instructions: &PluginMentionInstructionsContext,
|
||||
) -> Vec<ResponseItem> {
|
||||
// TODO(ccunningham): build_settings_update_items still does not cover every
|
||||
// model-visible item emitted by build_initial_context. Persist the remaining
|
||||
@@ -210,7 +213,10 @@ pub(crate) fn build_settings_update_items(
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(DeveloperInstructions::into_text)
|
||||
.collect();
|
||||
.chain(build_plugin_mention_developer_sections(
|
||||
plugin_mention_instructions,
|
||||
))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut items = Vec::with_capacity(2);
|
||||
if let Some(developer_message) = build_developer_update_item(developer_update_sections) {
|
||||
|
||||
@@ -15,6 +15,7 @@ use codex_protocol::models::is_image_open_tag_text;
|
||||
use codex_protocol::models::is_local_image_close_tag_text;
|
||||
use codex_protocol::models::is_local_image_open_tag_text;
|
||||
use codex_protocol::protocol::COLLABORATION_MODE_OPEN_TAG;
|
||||
use codex_protocol::protocol::PLUGIN_MENTION_INSTRUCTIONS_OPEN_TAG;
|
||||
use codex_protocol::protocol::REALTIME_CONVERSATION_OPEN_TAG;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use tracing::warn;
|
||||
@@ -28,6 +29,7 @@ const CONTEXTUAL_DEVELOPER_PREFIXES: &[&str] = &[
|
||||
"<permissions instructions>",
|
||||
"<model_switch>",
|
||||
COLLABORATION_MODE_OPEN_TAG,
|
||||
PLUGIN_MENTION_INSTRUCTIONS_OPEN_TAG,
|
||||
REALTIME_CONVERSATION_OPEN_TAG,
|
||||
"<personality_spec>",
|
||||
];
|
||||
|
||||
@@ -1,29 +1,40 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use codex_protocol::models::DeveloperInstructions;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
|
||||
use crate::connectors;
|
||||
use crate::mcp::CODEX_APPS_MCP_SERVER_NAME;
|
||||
use crate::mcp_connection_manager::ToolInfo;
|
||||
use crate::plugins::PluginCapabilitySummary;
|
||||
use crate::plugins::render_explicit_plugin_instructions;
|
||||
use crate::plugins::render_plugin_mention_instructions;
|
||||
|
||||
pub(crate) fn build_plugin_injections(
|
||||
/// Turn-local data needed to render explicit plugin-mention guidance inside the
|
||||
/// canonical pre-user developer envelope.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub(crate) struct PluginMentionInstructionsContext {
|
||||
entries: Vec<PluginMentionInstructionsEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct PluginMentionInstructionsEntry {
|
||||
plugin: PluginCapabilitySummary,
|
||||
available_mcp_servers: Vec<String>,
|
||||
available_apps: Vec<String>,
|
||||
}
|
||||
|
||||
/// Capture the turn-local plugin/tool/app ingredients needed to render explicit plugin guidance
|
||||
/// later in the canonical context builders, without re-listing MCP tools.
|
||||
pub(crate) fn build_plugin_mention_instructions_context(
|
||||
mentioned_plugins: &[PluginCapabilitySummary],
|
||||
mcp_tools: &HashMap<String, ToolInfo>,
|
||||
available_connectors: &[connectors::AppInfo],
|
||||
) -> Vec<ResponseItem> {
|
||||
) -> PluginMentionInstructionsContext {
|
||||
if mentioned_plugins.is_empty() {
|
||||
return Vec::new();
|
||||
return PluginMentionInstructionsContext::default();
|
||||
}
|
||||
|
||||
// Turn each explicit plugin mention into a developer hint that points the
|
||||
// model at the plugin's visible MCP servers, enabled apps, and skill prefix.
|
||||
mentioned_plugins
|
||||
let entries = mentioned_plugins
|
||||
.iter()
|
||||
.filter_map(|plugin| {
|
||||
.map(|plugin| {
|
||||
let available_mcp_servers = mcp_tools
|
||||
.values()
|
||||
.filter(|tool| {
|
||||
@@ -50,9 +61,36 @@ pub(crate) fn build_plugin_injections(
|
||||
.collect::<BTreeSet<String>>()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
render_explicit_plugin_instructions(plugin, &available_mcp_servers, &available_apps)
|
||||
.map(DeveloperInstructions::new)
|
||||
.map(ResponseItem::from)
|
||||
|
||||
PluginMentionInstructionsEntry {
|
||||
plugin: plugin.clone(),
|
||||
available_mcp_servers,
|
||||
available_apps,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
PluginMentionInstructionsContext { entries }
|
||||
}
|
||||
|
||||
/// Render plugin-mention guidance from the already-resolved per-turn plugin context.
|
||||
///
|
||||
/// The live turn path builds `PluginMentionInstructionsContext` once from the current turn's
|
||||
/// plugin/tool/app inventory, then whichever canonical context builder runs uses this renderer.
|
||||
pub(crate) fn build_plugin_mention_developer_sections(
|
||||
plugin_mention_instructions: &PluginMentionInstructionsContext,
|
||||
) -> Vec<String> {
|
||||
// Turn each explicit plugin mention into developer-message sections that
|
||||
// can be folded into the canonical pre-user developer envelope for this turn.
|
||||
plugin_mention_instructions
|
||||
.entries
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
render_plugin_mention_instructions(
|
||||
&entry.plugin,
|
||||
&entry.available_mcp_servers,
|
||||
&entry.available_apps,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -12,7 +12,9 @@ pub(crate) mod test_support;
|
||||
mod toggles;
|
||||
|
||||
pub(crate) use discoverable::list_tool_suggest_discoverable_plugins;
|
||||
pub(crate) use injection::build_plugin_injections;
|
||||
pub(crate) use injection::PluginMentionInstructionsContext;
|
||||
pub(crate) use injection::build_plugin_mention_developer_sections;
|
||||
pub(crate) use injection::build_plugin_mention_instructions_context;
|
||||
pub use manager::AppConnectorId;
|
||||
pub use manager::ConfiguredMarketplace;
|
||||
pub use manager::ConfiguredMarketplaceListOutcome;
|
||||
@@ -48,7 +50,7 @@ pub use marketplace::MarketplacePluginPolicy;
|
||||
pub use marketplace::MarketplacePluginSource;
|
||||
pub use remote::RemotePluginFetchError;
|
||||
pub use remote::fetch_remote_featured_plugin_ids;
|
||||
pub(crate) use render::render_explicit_plugin_instructions;
|
||||
pub(crate) use render::render_plugin_mention_instructions;
|
||||
pub(crate) use render::render_plugins_section;
|
||||
pub(crate) use startup_sync::curated_plugins_repo_path;
|
||||
pub(crate) use startup_sync::read_curated_plugins_sha;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::plugins::PluginCapabilitySummary;
|
||||
use codex_protocol::protocol::PLUGIN_MENTION_INSTRUCTIONS_CLOSE_TAG;
|
||||
use codex_protocol::protocol::PLUGIN_MENTION_INSTRUCTIONS_OPEN_TAG;
|
||||
use codex_protocol::protocol::PLUGINS_INSTRUCTIONS_CLOSE_TAG;
|
||||
use codex_protocol::protocol::PLUGINS_INSTRUCTIONS_OPEN_TAG;
|
||||
|
||||
@@ -39,7 +41,7 @@ pub(crate) fn render_plugins_section(plugins: &[PluginCapabilitySummary]) -> Opt
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn render_explicit_plugin_instructions(
|
||||
pub(crate) fn render_plugin_mention_instructions(
|
||||
plugin: &PluginCapabilitySummary,
|
||||
available_mcp_servers: &[String],
|
||||
available_apps: &[String],
|
||||
@@ -84,7 +86,10 @@ pub(crate) fn render_explicit_plugin_instructions(
|
||||
|
||||
lines.push("Use these plugin-associated capabilities to help solve the task.".to_string());
|
||||
|
||||
Some(lines.join("\n"))
|
||||
let body = lines.join("\n");
|
||||
Some(format!(
|
||||
"{PLUGIN_MENTION_INSTRUCTIONS_OPEN_TAG}\n{body}\n{PLUGIN_MENTION_INSTRUCTIONS_CLOSE_TAG}"
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -21,3 +21,22 @@ fn render_plugins_section_includes_descriptions_and_skill_naming_guidance() {
|
||||
|
||||
assert_eq!(rendered, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_plugin_mention_instructions_wraps_turn_local_guidance_in_stable_tag() {
|
||||
let rendered = render_plugin_mention_instructions(
|
||||
&PluginCapabilitySummary {
|
||||
config_name: "sample@test".to_string(),
|
||||
display_name: "sample".to_string(),
|
||||
has_skills: true,
|
||||
..PluginCapabilitySummary::default()
|
||||
},
|
||||
&[],
|
||||
&["Google Calendar".to_string()],
|
||||
)
|
||||
.expect("explicit plugin instructions should render");
|
||||
|
||||
let expected = "<plugin_mention_instructions>\nCapabilities from the `sample` plugin:\n- Skills from this plugin are prefixed with `sample:`.\n- Apps from this plugin available in this session: `Google Calendar`.\nUse these plugin-associated capabilities to help solve the task.\n</plugin_mention_instructions>";
|
||||
|
||||
assert_eq!(rendered, expected);
|
||||
}
|
||||
|
||||
@@ -195,7 +195,9 @@ fn out_of_range_truncation_drops_pre_user_active_turn_prefix() {
|
||||
#[tokio::test]
|
||||
async fn ignores_session_prefix_messages_when_truncating() {
|
||||
let (session, turn_context) = make_session_and_context().await;
|
||||
let mut items = session.build_initial_context(&turn_context).await;
|
||||
let mut items = session
|
||||
.build_initial_context(&turn_context, /*plugin_mention_instructions*/ None)
|
||||
.await;
|
||||
items.push(user_msg("feature request"));
|
||||
items.push(assistant_msg("ack"));
|
||||
items.push(user_msg("second question"));
|
||||
|
||||
@@ -124,7 +124,9 @@ fn truncates_rollout_from_start_applies_thread_rollback_markers() {
|
||||
#[tokio::test]
|
||||
async fn ignores_session_prefix_messages_when_truncating_rollout_from_start() {
|
||||
let (session, turn_context) = make_session_and_context().await;
|
||||
let mut items = session.build_initial_context(&turn_context).await;
|
||||
let mut items = session
|
||||
.build_initial_context(&turn_context, /*plugin_mention_instructions*/ None)
|
||||
.await;
|
||||
items.push(user_msg("feature request"));
|
||||
items.push(assistant_msg("ack"));
|
||||
items.push(user_msg("second question"));
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::sync::OnceLock;
|
||||
|
||||
use crate::responses::ResponsesRequest;
|
||||
use codex_protocol::protocol::APPS_INSTRUCTIONS_OPEN_TAG;
|
||||
use codex_protocol::protocol::PLUGIN_MENTION_INSTRUCTIONS_OPEN_TAG;
|
||||
use codex_protocol::protocol::PLUGINS_INSTRUCTIONS_OPEN_TAG;
|
||||
use codex_protocol::protocol::SKILLS_INSTRUCTIONS_OPEN_TAG;
|
||||
|
||||
@@ -281,6 +282,9 @@ fn canonicalize_snapshot_text(text: &str) -> String {
|
||||
if text.starts_with(PLUGINS_INSTRUCTIONS_OPEN_TAG) {
|
||||
return "<PLUGINS_INSTRUCTIONS>".to_string();
|
||||
}
|
||||
if text.starts_with(PLUGIN_MENTION_INSTRUCTIONS_OPEN_TAG) {
|
||||
return "<PLUGIN_MENTION_INSTRUCTIONS>".to_string();
|
||||
}
|
||||
if text.starts_with("# AGENTS.md instructions for ") {
|
||||
return "<AGENTS_MD>".to_string();
|
||||
}
|
||||
|
||||
@@ -10,7 +10,13 @@ use codex_core::CodexAuth;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::Op;
|
||||
use codex_protocol::protocol::PLUGIN_MENTION_INSTRUCTIONS_OPEN_TAG;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use core_test_support::apps_test_server::AppsTestServer;
|
||||
use core_test_support::context_snapshot;
|
||||
use core_test_support::context_snapshot::ContextSnapshotOptions;
|
||||
use core_test_support::context_snapshot::ContextSnapshotRenderMode;
|
||||
use core_test_support::responses::ResponsesRequest;
|
||||
use core_test_support::responses::ev_completed;
|
||||
use core_test_support::responses::ev_response_created;
|
||||
use core_test_support::responses::mount_sse_once;
|
||||
@@ -184,6 +190,24 @@ fn tool_description(body: &serde_json::Value, tool_name: &str) -> Option<String>
|
||||
})
|
||||
}
|
||||
|
||||
fn plugin_snapshot_options() -> ContextSnapshotOptions {
|
||||
ContextSnapshotOptions::default()
|
||||
.strip_capability_instructions()
|
||||
.strip_agents_md_user_context()
|
||||
.render_mode(ContextSnapshotRenderMode::KindWithTextPrefix { max_chars: 220 })
|
||||
}
|
||||
|
||||
fn format_labeled_requests_snapshot(
|
||||
scenario: &str,
|
||||
sections: &[(&str, &ResponsesRequest)],
|
||||
) -> String {
|
||||
context_snapshot::format_labeled_requests_snapshot(
|
||||
scenario,
|
||||
sections,
|
||||
&plugin_snapshot_options(),
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn capability_sections_render_in_developer_message_in_order() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
@@ -266,15 +290,7 @@ async fn explicit_plugin_mentions_inject_plugin_guidance() -> Result<()> {
|
||||
.await;
|
||||
|
||||
let codex_home = Arc::new(TempDir::new()?);
|
||||
let rmcp_test_server_bin = match stdio_server_bin() {
|
||||
Ok(bin) => bin,
|
||||
Err(err) => {
|
||||
eprintln!("test_stdio_server binary not available, skipping test: {err}");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
write_plugin_skill_plugin(codex_home.as_ref());
|
||||
write_plugin_mcp_plugin(codex_home.as_ref(), &rmcp_test_server_bin);
|
||||
write_plugin_app_plugin(codex_home.as_ref());
|
||||
|
||||
let codex =
|
||||
@@ -283,34 +299,48 @@ async fn explicit_plugin_mentions_inject_plugin_guidance() -> Result<()> {
|
||||
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
items: vec![codex_protocol::user_input::UserInput::Mention {
|
||||
name: "sample".into(),
|
||||
path: format!("plugin://{SAMPLE_PLUGIN_CONFIG_NAME}"),
|
||||
}],
|
||||
items: vec![
|
||||
UserInput::Mention {
|
||||
name: "sample".into(),
|
||||
path: format!("plugin://{SAMPLE_PLUGIN_CONFIG_NAME}"),
|
||||
},
|
||||
UserInput::Text {
|
||||
text: "help me inspect the sample plugin".into(),
|
||||
text_elements: Vec::new(),
|
||||
},
|
||||
],
|
||||
final_output_json_schema: None,
|
||||
})
|
||||
.await?;
|
||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||
|
||||
let request = mock.single_request();
|
||||
insta::assert_snapshot!(
|
||||
"explicit_plugin_mentions_inject_plugin_guidance",
|
||||
format_labeled_requests_snapshot(
|
||||
"Explicit plugin mention request layout",
|
||||
&[("Plugin Mention Request", &request)]
|
||||
)
|
||||
);
|
||||
let developer_messages = request.message_input_texts("developer");
|
||||
assert!(
|
||||
developer_messages
|
||||
.iter()
|
||||
.any(|text| text.contains("Skills from this plugin")),
|
||||
"expected plugin skills guidance: {developer_messages:?}"
|
||||
.any(|text| text.contains(PLUGIN_MENTION_INSTRUCTIONS_OPEN_TAG)),
|
||||
"expected plugin mention guidance in developer messages: {developer_messages:?}"
|
||||
);
|
||||
let user_messages = request.message_input_texts("user");
|
||||
assert!(
|
||||
user_messages
|
||||
.iter()
|
||||
.all(|text| !text.contains(PLUGIN_MENTION_INSTRUCTIONS_OPEN_TAG)),
|
||||
"expected plugin mention guidance to stay out of user messages: {user_messages:?}"
|
||||
);
|
||||
assert!(
|
||||
developer_messages
|
||||
.iter()
|
||||
.any(|text| text.contains("MCP servers from this plugin")),
|
||||
"expected visible plugin MCP guidance: {developer_messages:?}"
|
||||
);
|
||||
assert!(
|
||||
developer_messages
|
||||
.iter()
|
||||
.any(|text| text.contains("Apps from this plugin")),
|
||||
"expected visible plugin app guidance: {developer_messages:?}"
|
||||
.any(|text| text.contains("Apps from this plugin available in this session")),
|
||||
"expected plugin guidance in developer messages: {developer_messages:?}"
|
||||
);
|
||||
let request_body = request.body_json();
|
||||
let request_tools = tool_names(&request_body);
|
||||
@@ -320,12 +350,6 @@ async fn explicit_plugin_mentions_inject_plugin_guidance() -> Result<()> {
|
||||
.any(|name| name == "mcp__codex_apps__google_calendar_create_event"),
|
||||
"expected plugin app tools to become visible for this turn: {request_tools:?}"
|
||||
);
|
||||
let echo_description = tool_description(&request_body, "mcp__sample__echo")
|
||||
.expect("plugin MCP tool description should be present");
|
||||
assert!(
|
||||
echo_description.contains("This tool is part of plugin `sample`."),
|
||||
"expected plugin MCP provenance in tool description: {echo_description:?}"
|
||||
);
|
||||
let calendar_description = tool_description(
|
||||
&request_body,
|
||||
"mcp__codex_apps__google_calendar_create_event",
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
source: core/tests/suite/plugins.rs
|
||||
expression: "format_labeled_requests_snapshot(\"Explicit plugin mention request layout\",\n&[(\"Plugin Mention Request\", &request)])"
|
||||
---
|
||||
Scenario: Explicit plugin mention request layout
|
||||
|
||||
## Plugin Mention Request
|
||||
00:message/developer[2]:
|
||||
[01] <PERMISSIONS_INSTRUCTIONS>
|
||||
[02] <PLUGIN_MENTION_INSTRUCTIONS>
|
||||
01:message/user:<ENVIRONMENT_CONTEXT:cwd=<CWD>>
|
||||
02:message/user:help me inspect the sample plugin
|
||||
@@ -95,6 +95,8 @@ pub const SKILLS_INSTRUCTIONS_OPEN_TAG: &str = "<skills_instructions>";
|
||||
pub const SKILLS_INSTRUCTIONS_CLOSE_TAG: &str = "</skills_instructions>";
|
||||
pub const PLUGINS_INSTRUCTIONS_OPEN_TAG: &str = "<plugins_instructions>";
|
||||
pub const PLUGINS_INSTRUCTIONS_CLOSE_TAG: &str = "</plugins_instructions>";
|
||||
pub const PLUGIN_MENTION_INSTRUCTIONS_OPEN_TAG: &str = "<plugin_mention_instructions>";
|
||||
pub const PLUGIN_MENTION_INSTRUCTIONS_CLOSE_TAG: &str = "</plugin_mention_instructions>";
|
||||
pub const COLLABORATION_MODE_OPEN_TAG: &str = "<collaboration_mode>";
|
||||
pub const COLLABORATION_MODE_CLOSE_TAG: &str = "</collaboration_mode>";
|
||||
pub const REALTIME_CONVERSATION_OPEN_TAG: &str = "<realtime_conversation>";
|
||||
|
||||
Reference in New Issue
Block a user