mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
Allow model catalogs to override incremental tool notices (#52736)
## What changed
Add `model_messages.tools.incremental_tools` overrides for the update hint, tool and namespace removal headers, and namespace instruction update and clearing messages. Missing, null, or over-512-byte values fall back to bundled wording; empty strings omit static text while retaining runtime names and instructions.
Substitute `{name}` in namespace message templates and append runtime instructions verbatim. Wording changes alone do not trigger tool catalog updates.
## Testing
Extend unit tests and MCP request-history snapshots to cover custom wording, empty notices, tool removal and restoration, and namespace instruction changes and clearing without repeating unchanged tools or notices.
GitOrigin-RevId: 22dea0a6fb4cfcd646f5e65346424834c8b7d5a2
This commit is contained in:
@@ -3188,7 +3188,9 @@ async fn spawn_agent_fork_strips_parent_usage_hints_from_compacted_history(marke
|
||||
role: "developer".to_string(),
|
||||
tools: vec![serde_json::json!({"type": "function", "name": "compacted_tool"})],
|
||||
};
|
||||
let tools_update = ContextualUserFragment::into(IncrementalToolsHint);
|
||||
let tools_update = ContextualUserFragment::into(IncrementalToolsHint(
|
||||
codex_prompts::ResolvedIncrementalToolMessages::default().tool_update_hint,
|
||||
));
|
||||
let replacement_history = vec![
|
||||
tools_update.clone(),
|
||||
tool_declarations.clone(),
|
||||
@@ -3700,7 +3702,9 @@ async fn spawn_agent_full_fork_legacy_compaction_rebuilds_child_instructions_onc
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
}));
|
||||
rollout_items.push(rollout_response_item(ContextualUserFragment::into(
|
||||
IncrementalToolsHint,
|
||||
IncrementalToolsHint(
|
||||
codex_prompts::ResolvedIncrementalToolMessages::default().tool_update_hint,
|
||||
),
|
||||
)));
|
||||
rollout_items.push(rollout_response_item(ResponseItem::AdditionalTools {
|
||||
id: None,
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::context::ContextualUserFragment;
|
||||
use crate::context::DeveloperInstructions;
|
||||
use codex_extension_api::ExtensionMetrics;
|
||||
use codex_otel::TOOL_INCREMENTAL_UPDATES_METRIC;
|
||||
use codex_prompts::ResolvedIncrementalToolMessages;
|
||||
use codex_protocol::error::CodexErrorDetails;
|
||||
use codex_protocol::error::Result;
|
||||
use codex_protocol::models::ContentItemKind;
|
||||
@@ -26,11 +27,9 @@ const ACTION_ADDED: &str = "added";
|
||||
const ACTION_REMOVED: &str = "removed";
|
||||
const ACTION_SCHEMA_CHANGED: &str = "schema_changed";
|
||||
|
||||
const REMOVED_TOOLS_HEADER: &str = "The following tools are no longer available. Do not call them:";
|
||||
const REMOVED_NAMESPACES_HEADER: &str = "The following namespaces are no longer available. Do not call tools in them unless those tools are declared in a later update:";
|
||||
|
||||
/// The exact serialized Responses Lite declarations visible in one captured step.
|
||||
pub(crate) struct TopLevelToolsState {
|
||||
messages: ResolvedIncrementalToolMessages,
|
||||
definitions: Vec<Value>,
|
||||
hashes: BTreeMap<String, WorldStateHash>,
|
||||
metrics: Option<Arc<dyn ExtensionMetrics>>,
|
||||
@@ -42,6 +41,7 @@ impl TopLevelToolsState {
|
||||
reason = "the Responses Lite serializer emits objects with array-valued namespace members"
|
||||
)]
|
||||
pub(crate) fn new(
|
||||
messages: ResolvedIncrementalToolMessages,
|
||||
definitions: Vec<Value>,
|
||||
metrics: Option<Arc<dyn ExtensionMetrics>>,
|
||||
) -> Result<Self> {
|
||||
@@ -68,6 +68,7 @@ impl TopLevelToolsState {
|
||||
insert_hash(name.to_string(), &header)?;
|
||||
}
|
||||
Ok(Self {
|
||||
messages,
|
||||
definitions,
|
||||
hashes,
|
||||
metrics,
|
||||
@@ -174,19 +175,31 @@ impl WorldStateSection for TopLevelToolsState {
|
||||
// repeating unchanged tool definitions.
|
||||
let instructions = definition["description"].as_str().unwrap_or_default();
|
||||
let text = if instructions.is_empty() {
|
||||
format!("The {name} namespace no longer has additional instructions.")
|
||||
self.messages
|
||||
.namespace_instructions_cleared
|
||||
.replace("{name}", name)
|
||||
} else {
|
||||
format!("Updated instructions for the {name} namespace:\n{instructions}")
|
||||
let mut text = self
|
||||
.messages
|
||||
.namespace_instructions_prefix
|
||||
.replace("{name}", name);
|
||||
text.push_str(instructions);
|
||||
text
|
||||
};
|
||||
namespace_updates
|
||||
.push(WorldStateUpdate::fragment(DeveloperInstructions::new(text)));
|
||||
if !text.is_empty() {
|
||||
namespace_updates
|
||||
.push(WorldStateUpdate::fragment(DeveloperInstructions::new(text)));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut updates = Vec::new();
|
||||
if !tools.is_empty() {
|
||||
if previous.is_some() && tools.iter().any(|tool| tool["type"] == "namespace") {
|
||||
if previous.is_some()
|
||||
&& tools.iter().any(|tool| tool["type"] == "namespace")
|
||||
&& !self.messages.tool_update_hint.is_empty()
|
||||
{
|
||||
updates.push(WorldStateUpdate::prefix_item(ContextualUserFragment::into(
|
||||
IncrementalToolsHint,
|
||||
IncrementalToolsHint(self.messages.tool_update_hint.clone()),
|
||||
)));
|
||||
}
|
||||
updates.push(WorldStateUpdate::prefix_item(
|
||||
@@ -225,6 +238,7 @@ impl WorldStateSection for TopLevelToolsState {
|
||||
if !namespaces.is_empty() || !tools.is_empty() {
|
||||
updates.push(
|
||||
WorldStateUpdate::fragment(RemovedTools {
|
||||
messages: self.messages.clone(),
|
||||
namespaces: namespaces.into_iter().map(str::to_string).collect(),
|
||||
tools,
|
||||
})
|
||||
@@ -239,7 +253,7 @@ impl WorldStateSection for TopLevelToolsState {
|
||||
}
|
||||
|
||||
/// Guidance for an appended catalog batch, retained or discarded with its declarations.
|
||||
pub(crate) struct IncrementalToolsHint;
|
||||
pub(crate) struct IncrementalToolsHint(pub(crate) String);
|
||||
|
||||
impl IncrementalToolsHint {
|
||||
pub(crate) const KIND: &str = "tools.incremental_update";
|
||||
@@ -263,12 +277,13 @@ impl ContextualUserFragment for IncrementalToolsHint {
|
||||
}
|
||||
|
||||
fn body(&self) -> String {
|
||||
"This is an incremental tools update. Previously declared tools remain available for direct calls unless explicitly marked unavailable. If a tool is redefined here, its latest definition replaces the earlier one.".to_string()
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct RemovedTools {
|
||||
messages: ResolvedIncrementalToolMessages,
|
||||
namespaces: Vec<String>,
|
||||
tools: Vec<String>,
|
||||
}
|
||||
@@ -293,8 +308,11 @@ impl ContextualUserFragment for RemovedTools {
|
||||
/// Lists removed namespaces and individual tools in separate sections of one notice.
|
||||
fn body(&self) -> String {
|
||||
let sections = [
|
||||
(REMOVED_NAMESPACES_HEADER, &self.namespaces),
|
||||
(REMOVED_TOOLS_HEADER, &self.tools),
|
||||
(
|
||||
self.messages.removed_namespaces_header.as_str(),
|
||||
&self.namespaces,
|
||||
),
|
||||
(self.messages.removed_tools_header.as_str(), &self.tools),
|
||||
];
|
||||
let mut body = String::new();
|
||||
for (header, names) in sections {
|
||||
|
||||
@@ -42,12 +42,32 @@ fn snapshots_catalog_transitions() {
|
||||
"description": "Search tools.",
|
||||
"parameters": {"type": "object", "properties": {}}});
|
||||
let web = json!({"type": "web_search", "external_web_access": true});
|
||||
let original = TopLevelToolsState::new(vec![search.clone()], /*metrics*/ None).unwrap();
|
||||
let original = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
vec![search.clone()],
|
||||
/*metrics*/ None,
|
||||
)
|
||||
.unwrap();
|
||||
let mut changed = search;
|
||||
changed["parameters"]["properties"] = json!({"query": {"type": "string"}});
|
||||
let updated = TopLevelToolsState::new(vec![changed, web.clone()], /*metrics*/ None).unwrap();
|
||||
let only_web = TopLevelToolsState::new(vec![web], /*metrics*/ None).unwrap();
|
||||
let empty = TopLevelToolsState::new(Vec::new(), /*metrics*/ None).unwrap();
|
||||
let updated = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
vec![changed, web.clone()],
|
||||
/*metrics*/ None,
|
||||
)
|
||||
.unwrap();
|
||||
let only_web = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
vec![web],
|
||||
/*metrics*/ None,
|
||||
)
|
||||
.unwrap();
|
||||
let empty = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
Vec::new(),
|
||||
/*metrics*/ None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
insta::assert_snapshot!(render_section_cases(&[
|
||||
(Absent, Known(&original)),
|
||||
@@ -90,6 +110,7 @@ async fn snapshots_namespace_transitions() {
|
||||
.with_metrics_without_metadata_tags(metrics.clone()),
|
||||
);
|
||||
let original = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
vec![
|
||||
namespace(
|
||||
"functions",
|
||||
@@ -107,6 +128,7 @@ async fn snapshots_namespace_transitions() {
|
||||
let mut changed = declaration("changed");
|
||||
changed["parameters"]["properties"] = json!({"query": {"type": "string"}});
|
||||
let updated = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
vec![
|
||||
namespace(
|
||||
"functions",
|
||||
@@ -118,17 +140,25 @@ async fn snapshots_namespace_transitions() {
|
||||
)
|
||||
.unwrap();
|
||||
let only_other = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
vec![namespace("other", vec![declaration("lookup")])],
|
||||
Some(Arc::clone(&telemetry)),
|
||||
)
|
||||
.unwrap();
|
||||
let only_functions = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
vec![namespace("functions", vec![declaration("unchanged")])],
|
||||
Some(Arc::clone(&telemetry)),
|
||||
)
|
||||
.unwrap();
|
||||
let empty = TopLevelToolsState::new(Vec::new(), Some(Arc::clone(&telemetry))).unwrap();
|
||||
let empty = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
Vec::new(),
|
||||
Some(Arc::clone(&telemetry)),
|
||||
)
|
||||
.unwrap();
|
||||
let restored = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
vec![namespace(
|
||||
"functions",
|
||||
vec![declaration("unchanged"), declaration("restored")],
|
||||
@@ -191,38 +221,71 @@ fn initial_catalog_preserves_order_and_unchanged_catalog_emits_nothing() {
|
||||
namespace("zebra", vec![declaration("lookup")]),
|
||||
namespace("alpha", vec![declaration("lookup")]),
|
||||
];
|
||||
let state = TopLevelToolsState::new(definitions.clone(), /*metrics*/ None).unwrap();
|
||||
let state = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
definitions.clone(),
|
||||
/*metrics*/ None,
|
||||
)
|
||||
.unwrap();
|
||||
let (snapshot, updates) = state.render_diff(PreviousSectionState::Absent);
|
||||
assert_eq!(
|
||||
merge_world_state_updates(updates),
|
||||
vec![item(definitions.clone())]
|
||||
);
|
||||
let (_, updates) = state.render_diff(PreviousSectionState::Unknown);
|
||||
assert_eq!(merge_world_state_updates(updates), vec![item(definitions)]);
|
||||
assert_eq!(
|
||||
merge_world_state_updates(updates),
|
||||
vec![item(definitions.clone())]
|
||||
);
|
||||
let snapshot = snapshot.unwrap();
|
||||
assert_eq!(
|
||||
snapshot.keys().map(String::as_str).collect::<Vec<_>>(),
|
||||
vec!["alpha", "alpha.lookup", "zebra", "zebra.lookup"]
|
||||
);
|
||||
let (unchanged_snapshot, updates) = state.render_diff(PreviousSectionState::Known(&snapshot));
|
||||
assert_eq!(unchanged_snapshot, Some(snapshot));
|
||||
assert_eq!(unchanged_snapshot, Some(snapshot.clone()));
|
||||
assert!(updates.is_empty());
|
||||
|
||||
let reworded = TopLevelToolsState::new(
|
||||
ResolvedIncrementalToolMessages {
|
||||
tool_update_hint: "New wording".to_string(),
|
||||
removed_tools_header: "Removed".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
definitions,
|
||||
/*metrics*/ None,
|
||||
)
|
||||
.unwrap();
|
||||
let (reworded_snapshot, updates) = reworded.render_diff(PreviousSectionState::Known(&snapshot));
|
||||
assert_eq!(reworded_snapshot, Some(snapshot));
|
||||
assert!(updates.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_catalog_is_persisted_and_can_add_tools_again() {
|
||||
let definitions = vec![namespace("functions", vec![declaration("lookup")])];
|
||||
let original = TopLevelToolsState::new(definitions.clone(), /*metrics*/ None).unwrap();
|
||||
let original = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
definitions.clone(),
|
||||
/*metrics*/ None,
|
||||
)
|
||||
.unwrap();
|
||||
let previous = original
|
||||
.render_diff(PreviousSectionState::Absent)
|
||||
.0
|
||||
.unwrap();
|
||||
let empty = TopLevelToolsState::new(Vec::new(), /*metrics*/ None).unwrap();
|
||||
let empty = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
Vec::new(),
|
||||
/*metrics*/ None,
|
||||
)
|
||||
.unwrap();
|
||||
let (snapshot, updates) = empty.render_diff(PreviousSectionState::Known(&previous));
|
||||
assert_eq!(snapshot, Some(BTreeMap::new()));
|
||||
assert_eq!(
|
||||
merge_world_state_updates(updates),
|
||||
vec![ContextualUserFragment::into(RemovedTools {
|
||||
messages: Default::default(),
|
||||
namespaces: vec!["functions".to_string()],
|
||||
..Default::default()
|
||||
})]
|
||||
@@ -231,7 +294,9 @@ fn empty_catalog_is_persisted_and_can_add_tools_again() {
|
||||
assert_eq!(
|
||||
merge_world_state_updates(updates),
|
||||
vec![
|
||||
ContextualUserFragment::into(IncrementalToolsHint),
|
||||
ContextualUserFragment::into(IncrementalToolsHint(
|
||||
ResolvedIncrementalToolMessages::default().tool_update_hint
|
||||
)),
|
||||
item(definitions),
|
||||
]
|
||||
);
|
||||
@@ -240,6 +305,7 @@ fn empty_catalog_is_persisted_and_can_add_tools_again() {
|
||||
#[test]
|
||||
fn namespace_diff_contains_only_changed_and_added_tools() {
|
||||
let original = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
vec![
|
||||
namespace(
|
||||
"functions",
|
||||
@@ -262,6 +328,7 @@ fn namespace_diff_contains_only_changed_and_added_tools() {
|
||||
changed["parameters"]["properties"] = json!({"query": {"type": "string"}});
|
||||
let added = declaration("added");
|
||||
let current = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
vec![
|
||||
namespace(
|
||||
"functions",
|
||||
@@ -277,9 +344,12 @@ fn namespace_diff_contains_only_changed_and_added_tools() {
|
||||
assert_eq!(
|
||||
merge_world_state_updates(updates),
|
||||
vec![
|
||||
ContextualUserFragment::into(IncrementalToolsHint),
|
||||
ContextualUserFragment::into(IncrementalToolsHint(
|
||||
ResolvedIncrementalToolMessages::default().tool_update_hint
|
||||
)),
|
||||
item(vec![expected]),
|
||||
ContextualUserFragment::into(RemovedTools {
|
||||
messages: Default::default(),
|
||||
tools: vec!["functions.removed".to_string()],
|
||||
..Default::default()
|
||||
}),
|
||||
@@ -296,13 +366,19 @@ fn namespace_diff_contains_only_changed_and_added_tools() {
|
||||
#[test]
|
||||
fn new_namespaces_share_one_notice_before_their_declarations() {
|
||||
let original = namespace("original", vec![declaration("lookup")]);
|
||||
let state = TopLevelToolsState::new(vec![original.clone()], /*metrics*/ None).unwrap();
|
||||
let state = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
vec![original.clone()],
|
||||
/*metrics*/ None,
|
||||
)
|
||||
.unwrap();
|
||||
let previous = state.render_diff(PreviousSectionState::Absent).0.unwrap();
|
||||
let additions = vec![
|
||||
namespace("second", vec![declaration("lookup")]),
|
||||
namespace("third", vec![declaration("lookup")]),
|
||||
];
|
||||
let state = TopLevelToolsState::new(
|
||||
let mut state = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
std::iter::once(original).chain(additions.clone()).collect(),
|
||||
/*metrics*/ None,
|
||||
)
|
||||
@@ -312,11 +388,18 @@ fn new_namespaces_share_one_notice_before_their_declarations() {
|
||||
assert_eq!(
|
||||
prefix,
|
||||
vec![
|
||||
ContextualUserFragment::into(IncrementalToolsHint),
|
||||
item(additions),
|
||||
ContextualUserFragment::into(IncrementalToolsHint(
|
||||
ResolvedIncrementalToolMessages::default().tool_update_hint
|
||||
)),
|
||||
item(additions.clone()),
|
||||
]
|
||||
);
|
||||
assert!(remaining.is_empty());
|
||||
state.messages.tool_update_hint.clear();
|
||||
let (_, updates) = state.render_diff(PreviousSectionState::Known(&previous));
|
||||
let (prefix, remaining) = super::super::split_prefix_updates(updates);
|
||||
assert_eq!(prefix, vec![item(additions)]);
|
||||
assert!(remaining.is_empty());
|
||||
assert!(
|
||||
state
|
||||
.render_diff(PreviousSectionState::Known(&snapshot.unwrap()))
|
||||
@@ -325,9 +408,34 @@ fn new_namespaces_share_one_notice_before_their_declarations() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn namespace_description_change_does_not_repeat_unchanged_tools() {
|
||||
#[test_case::test_case(
|
||||
None,
|
||||
"Updated namespace guidance.",
|
||||
"Updated instructions for the functions namespace:\nUpdated namespace guidance."
|
||||
)]
|
||||
#[test_case::test_case(
|
||||
Some(("Catalog {name}: ", "Cleared {name}.")),
|
||||
"Literal {name} and {instructions}.",
|
||||
"Catalog functions: Literal {name} and {instructions}."
|
||||
)]
|
||||
#[test_case::test_case(
|
||||
Some(("", "Cleared {name}.")),
|
||||
"Runtime instructions.",
|
||||
"Runtime instructions."
|
||||
)]
|
||||
#[test_case::test_case(
|
||||
Some(("Catalog {name}: ", "Cleared {name}.")),
|
||||
"",
|
||||
"Cleared functions."
|
||||
)]
|
||||
#[test_case::test_case(Some(("Catalog {name}: ", "")), "", "")]
|
||||
fn namespace_description_change_does_not_repeat_unchanged_tools(
|
||||
wording: Option<(&str, &str)>,
|
||||
instructions: &str,
|
||||
expected: &str,
|
||||
) {
|
||||
let original = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
vec![namespace("functions", vec![declaration("lookup")])],
|
||||
/*metrics*/ None,
|
||||
)
|
||||
@@ -337,14 +445,23 @@ fn namespace_description_change_does_not_repeat_unchanged_tools() {
|
||||
.0
|
||||
.unwrap();
|
||||
let mut updated = namespace("functions", vec![declaration("lookup")]);
|
||||
updated["description"] = json!("Updated namespace guidance.");
|
||||
let current = TopLevelToolsState::new(vec![updated], /*metrics*/ None).unwrap();
|
||||
updated["description"] = json!(instructions);
|
||||
let mut messages = ResolvedIncrementalToolMessages::default();
|
||||
if let Some((prefix, cleared_template)) = wording {
|
||||
messages.namespace_instructions_prefix = prefix.to_string();
|
||||
messages.namespace_instructions_cleared = cleared_template.to_string();
|
||||
}
|
||||
let current = TopLevelToolsState::new(messages, vec![updated], /*metrics*/ None).unwrap();
|
||||
let (snapshot, updates) = current.render_diff(PreviousSectionState::Known(&snapshot));
|
||||
assert_eq!(
|
||||
merge_world_state_updates(updates),
|
||||
vec![ContextualUserFragment::into(DeveloperInstructions::new(
|
||||
"Updated instructions for the functions namespace:\nUpdated namespace guidance."
|
||||
))]
|
||||
if expected.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
vec![ContextualUserFragment::into(DeveloperInstructions::new(
|
||||
expected,
|
||||
))]
|
||||
}
|
||||
);
|
||||
assert!(
|
||||
current
|
||||
@@ -357,6 +474,7 @@ fn namespace_description_change_does_not_repeat_unchanged_tools() {
|
||||
#[test]
|
||||
fn tool_type_change_keeps_the_callable_name_available() {
|
||||
let original = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
vec![namespace(
|
||||
"functions",
|
||||
vec![declaration("lookup"), declaration("removed")],
|
||||
@@ -371,14 +489,22 @@ fn tool_type_change_keeps_the_callable_name_available() {
|
||||
let custom = json!({"type": "custom", "name": "lookup", "description": "Look up a value.",
|
||||
"format": {"type": "text"}});
|
||||
let updated = namespace("functions", vec![custom]);
|
||||
let current = TopLevelToolsState::new(vec![updated.clone()], /*metrics*/ None).unwrap();
|
||||
let current = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
vec![updated.clone()],
|
||||
/*metrics*/ None,
|
||||
)
|
||||
.unwrap();
|
||||
let (_, updates) = current.render_diff(PreviousSectionState::Known(&snapshot));
|
||||
assert_eq!(
|
||||
merge_world_state_updates(updates),
|
||||
vec![
|
||||
ContextualUserFragment::into(IncrementalToolsHint),
|
||||
ContextualUserFragment::into(IncrementalToolsHint(
|
||||
ResolvedIncrementalToolMessages::default().tool_update_hint
|
||||
)),
|
||||
item(vec![updated]),
|
||||
ContextualUserFragment::into(RemovedTools {
|
||||
messages: Default::default(),
|
||||
tools: vec!["functions.removed".to_string()],
|
||||
..Default::default()
|
||||
}),
|
||||
@@ -389,6 +515,7 @@ fn tool_type_change_keeps_the_callable_name_available() {
|
||||
#[test]
|
||||
fn namespace_removals_emit_one_notice() {
|
||||
let original = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
vec![
|
||||
namespace(
|
||||
"functions",
|
||||
@@ -410,6 +537,7 @@ fn namespace_removals_emit_one_notice() {
|
||||
.0
|
||||
.unwrap();
|
||||
let current = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
vec![namespace("functions", vec![declaration("lookup")])],
|
||||
/*metrics*/ None,
|
||||
)
|
||||
@@ -418,6 +546,7 @@ fn namespace_removals_emit_one_notice() {
|
||||
assert_eq!(
|
||||
merge_world_state_updates(updates),
|
||||
vec![ContextualUserFragment::into(RemovedTools {
|
||||
messages: Default::default(),
|
||||
namespaces: vec!["function".to_string(), "other".to_string()],
|
||||
tools: vec![
|
||||
"functions.also_removed".to_string(),
|
||||
@@ -431,6 +560,7 @@ fn namespace_removals_emit_one_notice() {
|
||||
#[test]
|
||||
fn namespace_replaced_by_builtin_keeps_the_replacement_available() {
|
||||
let original = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
vec![namespace("web_search", vec![declaration("lookup")])],
|
||||
/*metrics*/ None,
|
||||
)
|
||||
@@ -440,13 +570,19 @@ fn namespace_replaced_by_builtin_keeps_the_replacement_available() {
|
||||
.0
|
||||
.unwrap();
|
||||
let replacement = json!({"type": "web_search"});
|
||||
let current = TopLevelToolsState::new(vec![replacement.clone()], /*metrics*/ None).unwrap();
|
||||
let current = TopLevelToolsState::new(
|
||||
/*messages*/ Default::default(),
|
||||
vec![replacement.clone()],
|
||||
/*metrics*/ None,
|
||||
)
|
||||
.unwrap();
|
||||
let (_, updates) = current.render_diff(PreviousSectionState::Known(&snapshot));
|
||||
assert_eq!(
|
||||
merge_world_state_updates(updates),
|
||||
vec![
|
||||
item(vec![replacement]),
|
||||
ContextualUserFragment::into(RemovedTools {
|
||||
messages: Default::default(),
|
||||
tools: vec!["web_search.lookup".to_string()],
|
||||
..Default::default()
|
||||
}),
|
||||
|
||||
@@ -147,6 +147,7 @@ impl Session {
|
||||
let specs = step_context.tool_router.model_visible_specs();
|
||||
let definitions = codex_tools::create_tools_json_for_responses_lite(&specs)?;
|
||||
world_state.add_section(TopLevelToolsState::new(
|
||||
model_messages.incremental_tools(),
|
||||
definitions,
|
||||
Some(Arc::clone(&extension_metrics)),
|
||||
)?);
|
||||
|
||||
Reference in New Issue
Block a user