Keep shared MCP types stable in Code Mode exec descriptions (#50536)

## Why

Changing a deferred tool's output schema could add or remove the shared MCP type preamble from the `exec` description.

## What changed

Include the default or configured MCP TypeScript preamble whenever `code_mode_only` is enabled, regardless of the available tools' output schemas. Preserve an empty preamble override as a way to omit the section.

## Testing

Add a regression test that checks description stability when a deferred tool gains an MCP output schema, covering default, custom, and empty preambles. Update description expectations, request snapshots, and two Guardian context-budget test thresholds for the added preamble.

GitOrigin-RevId: 725035d6159e9da3c4beff5482c426c082e768b4
This commit is contained in:
Ahmed Ibrahim
2026-10-03 05:51:19 +00:00
committed by copyberry
parent 7d5f55bdad
commit e1b5b56a4f
22 changed files with 113 additions and 70 deletions
+5 -11
View File
@@ -304,17 +304,11 @@ pub fn build_exec_tool_description(
return sections.join("\n\n");
}
let has_mcp_tools = enabled_tools
.iter()
.chain(deferred_tools)
.any(|tool| mcp_structured_content_schema(tool.output_schema.as_ref()).is_some());
if has_mcp_tools {
let preamble = messages
.and_then(|messages| messages.mcp_typescript_preamble.as_deref())
.unwrap_or(MCP_TYPESCRIPT_PREAMBLE);
if !preamble.is_empty() {
sections.push(format!("Shared MCP Types:\n```ts\n{preamble}\n```"));
}
let preamble = messages
.and_then(|messages| messages.mcp_typescript_preamble.as_deref())
.unwrap_or(MCP_TYPESCRIPT_PREAMBLE);
if !preamble.is_empty() {
sections.push(format!("Shared MCP Types:\n```ts\n{preamble}\n```"));
}
if !enabled_tools.is_empty() {
@@ -75,7 +75,14 @@ fn exec_override_preserves_empty_and_whitespace_only_text() {
..Default::default()
}),
),
description_override,
[
description_override,
"Shared MCP Types:\n```ts\nNo MCP tools; omit this.\n```"
]
.into_iter()
.filter(|section| !section.is_empty())
.collect::<Vec<_>>()
.join("\n\n"),
);
}
}
@@ -159,3 +166,45 @@ fn exec_override_preserves_runtime_sections() {
);
}
}
#[test]
fn mcp_types_stay_stable_when_a_deferred_tool_changes_its_output_schema() {
let mut tool = ToolDefinition {
name: "sample".to_string(),
tool_name: ToolName::plain("sample"),
description: "Deferred tool".to_string(),
kind: CodeModeToolKind::Function,
input_schema: None,
input_schema_max_bytes: None,
output_schema: None,
};
let render = |tool: &ToolDefinition, preamble: Option<&str>| {
build_exec_tool_description(
&[],
std::slice::from_ref(tool),
&BTreeMap::new(),
crate::DEFAULT_EXEC_YIELD_TIME_MS,
/*code_mode_only*/ true,
ImageDetailVisibility::Visible,
Some(&CodeModeToolMessages {
mcp_typescript_preamble: preamble.map(str::to_string),
..Default::default()
}),
)
};
// Keep one deferred tool throughout to isolate this from the independent
// discovery guidance transition when the entire deferred catalog empties.
for preamble in [None, Some("type CustomMcpResult = string;"), Some("")] {
tool.output_schema = None;
let before = render(&tool, preamble);
tool.output_schema = Some(json!({
"type": "object",
"properties": {
"content": { "type": "array", "items": { "type": "object" } },
"isError": { "type": "boolean" },
"_meta": { "type": "object" }
}
}));
assert_eq!(before, render(&tool, preamble));
}
}
@@ -6,8 +6,8 @@ Scenario: Guardian follow-up review request layout
## Window 1
-- request 1 (request; Initial Guardian Review Request) --
00:additional_tools/developer (1; hash=ae2f2f1f9dcd0cd9):
- namespace/functions; hash=fd8638eaf7cabb95
00:additional_tools/developer (1; hash=a8891bdc414756a2):
- namespace/functions; hash=af55b81b2cec6cc2
- custom/exec
- function/wait
01:message/developer:
@@ -6,8 +6,8 @@ Scenario: Guardian review request layout
## Window 1
-- request 1 (request; Guardian Review Request) --
00:additional_tools/developer (1; hash=ae2f2f1f9dcd0cd9):
- namespace/functions; hash=fd8638eaf7cabb95
00:additional_tools/developer (1; hash=a8891bdc414756a2):
- namespace/functions; hash=af55b81b2cec6cc2
- custom/exec
- function/wait
01:message/developer:
@@ -216,8 +216,8 @@ enum ReviewerResponse {
#[test_case(1, ReviewerResponse::Decision; "required_context_fails_closed")]
#[test_case(4_500, ReviewerResponse::ToolContinuation; "oversized_tool_continuation_compacts")]
#[test_case(4_500, ReviewerResponse::FileImageContinuation; "uploaded_original_image_history_compacts")]
#[test_case(4_500, ReviewerResponse::UncompactableContinuation; "ineffective_compaction_fails_closed")]
#[test_case(4_500, ReviewerResponse::CompactionError; "compaction_service_error_does_not_request_user_approval")]
#[test_case(5_000, ReviewerResponse::UncompactableContinuation; "ineffective_compaction_fails_closed")]
#[test_case(5_000, ReviewerResponse::CompactionError; "compaction_service_error_does_not_request_user_approval")]
#[test_case(6_000, ReviewerResponse::NextReview; "incoming_review_compacts_existing_history")]
async fn review_respects_complete_context_budget(
window: i64,
@@ -6,8 +6,8 @@ Scenario: Astra starts in one workspace, switches for the next step of the activ
## Window 1
-- request 1 (turn) --
00:additional_tools/developer (3; hash=a54e67018899e049):
- namespace/functions; hash=dead25325ac79c67
00:additional_tools/developer (3; hash=5c3ec9cbf2000b64):
- namespace/functions; hash=e6c4aaa15e6d6f30
- custom/exec
- function/wait
- function/request_user_input
@@ -7,8 +7,8 @@ Scenario: Astra uses the catalog question schema to ask who a launch update is f
## Window 1
-- request 1 (request) --
00:additional_tools/developer (3; hash=ae153e78f6722d2b):
- namespace/functions; hash=f1de44f63c977f3d
00:additional_tools/developer (3; hash=f19fc129e930a86e):
- namespace/functions; hash=b5c27b6b8551a052
- custom/exec
- function/wait
- function/request_user_input
@@ -6,8 +6,8 @@ Scenario: Astra receives host and handler timings on completed and failed code-m
## Window 1
-- request 1 (turn) --
00:additional_tools/developer (3; hash=a1bfbfab4176467b):
- namespace/functions; hash=e7db0a45c4ec3157
00:additional_tools/developer (3; hash=10256ca2e2118704):
- namespace/functions; hash=f01b11ec77d41982
- custom/exec
- function/wait
- function/request_user_input
@@ -16,8 +16,8 @@ Settings:
text: {"verbosity":"low"}
tool_choice: "auto"
-- request 1 (request) --
00:additional_tools/developer (3; hash=e33e09a2c1478914):
- namespace/functions; hash=99a630cff25563aa
00:additional_tools/developer (3; hash=1b07ea833720903d):
- namespace/functions; hash=986d556577441f79
- custom/exec
- function/wait
- function/request_user_input
@@ -6,8 +6,8 @@ Scenario: Astra receives new user input after its unfinished response is interru
## Window 1
-- request 1 (request) --
00:additional_tools/developer (3; hash=a1bfbfab4176467b):
- namespace/functions; hash=e7db0a45c4ec3157
00:additional_tools/developer (3; hash=10256ca2e2118704):
- namespace/functions; hash=f01b11ec77d41982
- custom/exec
- function/wait
- function/request_user_input
@@ -6,8 +6,8 @@ Scenario: Astra continues after new user input yields a running code cell, then
## Window 1
-- request 1 (turn) --
00:additional_tools/developer (3; hash=b035de0e31ab087e):
- namespace/functions; hash=a0b6a7ae1ecf9f28
00:additional_tools/developer (3; hash=7125a91d9e3d997f):
- namespace/functions; hash=5cf23dadab8c4adb
- custom/exec
- function/wait
- function/request_user_input
@@ -16,8 +16,8 @@ Settings:
text: {"verbosity":"low"}
tool_choice: "auto"
-- request 1 (turn) --
00:additional_tools/developer (3; hash=a1bfbfab4176467b):
- namespace/functions; hash=e7db0a45c4ec3157
00:additional_tools/developer (3; hash=10256ca2e2118704):
- namespace/functions; hash=f01b11ec77d41982
- custom/exec
- function/wait
- function/request_user_input
@@ -187,8 +187,8 @@ Settings:
## Window 2 (after request 3: input diverged at item 02)
Settings: same as window 1
-- request 4 (turn) --
00:additional_tools/developer (3; hash=a1bfbfab4176467b):
- namespace/functions; hash=e7db0a45c4ec3157
00:additional_tools/developer (3; hash=10256ca2e2118704):
- namespace/functions; hash=f01b11ec77d41982
- custom/exec
- function/wait
- function/request_user_input
@@ -16,8 +16,8 @@ Settings:
text: {"verbosity":"low"}
tool_choice: "auto"
-- request 1 (turn) --
00:additional_tools/developer (3; hash=a1bfbfab4176467b):
- namespace/functions; hash=e7db0a45c4ec3157
00:additional_tools/developer (3; hash=10256ca2e2118704):
- namespace/functions; hash=f01b11ec77d41982
- custom/exec
- function/wait
- function/request_user_input
@@ -16,8 +16,8 @@ Settings:
text: {"verbosity":"low"}
tool_choice: "auto"
-- request 1 (turn; Before /compact) --
00:additional_tools/developer (3; hash=a1bfbfab4176467b):
- namespace/functions; hash=e7db0a45c4ec3157
00:additional_tools/developer (3; hash=10256ca2e2118704):
- namespace/functions; hash=f01b11ec77d41982
- custom/exec
- function/wait
- function/request_user_input
@@ -115,8 +115,8 @@ Settings: unavailable (items only)
## Window 3 (after request 3: input diverged at item 00)
Settings: same as window 1
-- request 4 (turn; First turn after /compact) --
00:additional_tools/developer (3; hash=a1bfbfab4176467b):
- namespace/functions; hash=e7db0a45c4ec3157
00:additional_tools/developer (3; hash=10256ca2e2118704):
- namespace/functions; hash=f01b11ec77d41982
- custom/exec
- function/wait
- function/request_user_input
@@ -20,8 +20,8 @@ Scenario: An encrypted parent reply is delivered as a native agent message. Both
## Window 2 (after request 2: input diverged at item 00, settings changed (instructions, model, parallel_tool_calls, prompt_cache_key, reasoning, text, tools))
-- request 3 (turn) --
00:additional_tools/developer (1; hash=ae2f2f1f9dcd0cd9):
- namespace/functions; hash=fd8638eaf7cabb95
00:additional_tools/developer (1; hash=a8891bdc414756a2):
- namespace/functions; hash=af55b81b2cec6cc2
- custom/exec
- function/wait
01:message/developer:
@@ -106,8 +106,8 @@ Scenario: An encrypted parent reply is delivered as a native agent message. Both
## Window 4 (after request 4: input diverged at item 00, settings changed (instructions, model, parallel_tool_calls, prompt_cache_key, reasoning, text, tools))
-- request 5 (turn) --
00:additional_tools/developer (1; hash=ae2f2f1f9dcd0cd9):
- namespace/functions; hash=fd8638eaf7cabb95
00:additional_tools/developer (1; hash=a8891bdc414756a2):
- namespace/functions; hash=af55b81b2cec6cc2
- custom/exec
- function/wait
01:message/developer:
@@ -50,8 +50,8 @@ Settings: relative to window 1
reasoning: {"context":"all_turns","effort":"medium"}
tools: <removed>
-- request 4 (turn) --
00:additional_tools/developer (1; hash=2e71c3c832baebb6):
- namespace/functions; hash=cea33529f2099460
00:additional_tools/developer (1; hash=803cb4ba2a73d531):
- namespace/functions; hash=4b4ed79c9bae9f27
- custom/exec
- function/wait
- function/request_user_input
@@ -79,8 +79,8 @@ Settings: relative to window 2
reasoning: {"context":"all_turns","effort":"low"}
text: {"format":{"name":"codex_output_schema","schema":{"additionalProperties":false,"properties":{"outcome":{"enum":["allow","deny"],"type":"string"},"rationale":{"type":"string"},"risk_level":{"enum":["low","medium","high","critical"],"type":"string"},"user_authorization":{"enum":["unknown","low","medium","high"],"type":"string"}},"required":["outcome"],"type":"object"},"strict":false,"type":"json_schema"},"verbosity":"low"}
-- request 6 (turn) --
00:additional_tools/developer (1; hash=ae2f2f1f9dcd0cd9):
- namespace/functions; hash=fd8638eaf7cabb95
00:additional_tools/developer (1; hash=a8891bdc414756a2):
- namespace/functions; hash=af55b81b2cec6cc2
- custom/exec
- function/wait
01:message/developer:
@@ -187,8 +187,8 @@ Settings: relative to window 2
## Window 4 (after request 6: input diverged at item 00)
Settings: same as window 2
-- request 7 (turn) --
00:additional_tools/developer (1; hash=2e71c3c832baebb6):
- namespace/functions; hash=cea33529f2099460
00:additional_tools/developer (1; hash=803cb4ba2a73d531):
- namespace/functions; hash=4b4ed79c9bae9f27
- custom/exec
- function/wait
- function/request_user_input
@@ -216,8 +216,8 @@ Settings: same as window 2
## Window 5 (after request 8: input diverged at item 04)
Settings: same as window 2
-- request 9 (turn) --
00:additional_tools/developer (1; hash=2e71c3c832baebb6):
- namespace/functions; hash=cea33529f2099460
00:additional_tools/developer (1; hash=803cb4ba2a73d531):
- namespace/functions; hash=4b4ed79c9bae9f27
- custom/exec
- function/wait
- function/request_user_input
@@ -242,8 +242,8 @@ Settings: same as window 2
## Window 6 (after request 9: input diverged at item 00)
Settings: same as window 3
-- request 10 (turn) --
00:additional_tools/developer (1; hash=ae2f2f1f9dcd0cd9):
- namespace/functions; hash=fd8638eaf7cabb95
00:additional_tools/developer (1; hash=a8891bdc414756a2):
- namespace/functions; hash=af55b81b2cec6cc2
- custom/exec
- function/wait
01:message/developer:
@@ -340,8 +340,8 @@ Settings: same as window 3
## Window 7 (after request 10: input diverged at item 00)
Settings: same as window 2
-- request 11 (turn) --
00:additional_tools/developer (1; hash=2e71c3c832baebb6):
- namespace/functions; hash=cea33529f2099460
00:additional_tools/developer (1; hash=803cb4ba2a73d531):
- namespace/functions; hash=4b4ed79c9bae9f27
- custom/exec
- function/wait
- function/request_user_input
@@ -83,8 +83,8 @@ Scenario: A root uses Code Mode to send a confirmation and receives a real user
## Window 3 (after request 6: input diverged at item 00, settings changed (instructions, model, parallel_tool_calls, prompt_cache_key, reasoning, text, tools))
-- request 7 (turn) --
00:additional_tools/developer (1; hash=ae2f2f1f9dcd0cd9):
- namespace/functions; hash=fd8638eaf7cabb95
00:additional_tools/developer (1; hash=a8891bdc414756a2):
- namespace/functions; hash=af55b81b2cec6cc2
- custom/exec
- function/wait
01:message/developer:
@@ -153,8 +153,8 @@ Scenario: A root uses Code Mode to send a confirmation and receives a real user
## Window 4 (after request 7: input diverged at item 04, settings changed (prompt_cache_key))
-- request 8 (turn) --
00:additional_tools/developer (1; hash=ae2f2f1f9dcd0cd9):
- namespace/functions; hash=fd8638eaf7cabb95
00:additional_tools/developer (1; hash=a8891bdc414756a2):
- namespace/functions; hash=af55b81b2cec6cc2
- custom/exec
- function/wait
01:message/developer:
@@ -84,8 +84,8 @@ Settings: relative to window 1
text: {"format":{"name":"codex_output_schema","schema":{"additionalProperties":false,"properties":{"outcome":{"enum":["allow","deny"],"type":"string"},"rationale":{"type":"string"},"risk_level":{"enum":["low","medium","high","critical"],"type":"string"},"user_authorization":{"enum":["unknown","low","medium","high"],"type":"string"}},"required":["outcome"],"type":"object"},"strict":false,"type":"json_schema"},"verbosity":"low"}
tools: <removed>
-- request 2 (turn) --
00:additional_tools/developer (1; hash=ae2f2f1f9dcd0cd9):
- namespace/functions; hash=fd8638eaf7cabb95
00:additional_tools/developer (1; hash=a8891bdc414756a2):
- namespace/functions; hash=af55b81b2cec6cc2
- custom/exec
- function/wait
01:message/developer:
@@ -7,8 +7,8 @@ Scenario: Complete Guardian request history: root delegates through alpha to lea
## Window 1
-- request 1 (turn) --
00:additional_tools/developer (1; hash=ae2f2f1f9dcd0cd9):
- namespace/functions; hash=fd8638eaf7cabb95
00:additional_tools/developer (1; hash=a8891bdc414756a2):
- namespace/functions; hash=af55b81b2cec6cc2
- custom/exec
- function/wait
01:message/developer:
@@ -56,8 +56,8 @@ Scenario: Three scheduled runs share one instruction body, a human overrides its
## Window 2 (after request 5: input diverged at item 00, settings changed (instructions, model, parallel_tool_calls, prompt_cache_key, reasoning, text, tools))
-- request 6 (request) --
00:additional_tools/developer (1; hash=ae2f2f1f9dcd0cd9):
- namespace/functions; hash=fd8638eaf7cabb95
00:additional_tools/developer (1; hash=a8891bdc414756a2):
- namespace/functions; hash=af55b81b2cec6cc2
- custom/exec
- function/wait
01:message/developer:
@@ -16,8 +16,8 @@ Settings:
text: {"verbosity":"low"}
tool_choice: "auto"
-- request 1 (turn) --
00:additional_tools/developer (3; hash=78c6350cb8fdad81):
- namespace/functions; hash=e7db0a45c4ec3157
00:additional_tools/developer (3; hash=954d592d9c1e75d8):
- namespace/functions; hash=f01b11ec77d41982
- custom/exec
- function/wait
- function/request_user_input
@@ -16,8 +16,8 @@ Settings:
text: {"verbosity":"low"}
tool_choice: "auto"
-- request 1 (turn) --
00:additional_tools/developer (3; hash=853a5788ad239c35):
- namespace/functions; hash=48b2ce07cd26a39b
00:additional_tools/developer (3; hash=a1c4ca6659851eb8):
- namespace/functions; hash=8741ef7a7ba4ed8c
- custom/exec
- function/wait
- function/request_user_input