Support standalone tool outputs in turn/start (#41002)

## What changed

- Add an optional `toolOutput` to `turn/start` for starting or steering a turn with a named function-call output instead of user input.
- Emit standalone outputs as `functionCallOutput` thread items, persist them in durable history, and include them in resumed threads.
- Validate that tool outputs have a name, are not combined with nonempty `input`, satisfy the text-size limit, and use supported image URLs.

## Testing

- Cover standalone tool-output notifications, model input, and resumed history in both legacy and paginated history modes.
- Cover the text-size limit for structured tool output.

GitOrigin-RevId: a1eeec7b9b7de67b1a1c5e829a54552b85ec118b
This commit is contained in:
rka-oai
2026-08-26 23:58:10 +00:00
committed by copyberry
parent 2926014075
commit e56e4922eb
52 changed files with 3327 additions and 42 deletions
+13
View File
@@ -5,6 +5,7 @@ use crate::dynamic_tools::DynamicToolCallOutputContentItem;
use crate::mcp::CallToolResult;
use crate::memory_citation::MemoryCitation;
use crate::models::ContentItem;
use crate::models::FunctionCallOutputBody;
use crate::models::ImageDetail;
use crate::models::MessagePhase;
use crate::models::ResponseItem;
@@ -43,6 +44,7 @@ use ts_rs::TS;
#[ts(tag = "type")]
pub enum TurnItem {
UserMessage(UserMessageItem),
FunctionCallOutput(FunctionCallOutputItem),
HookPrompt(HookPromptItem),
AgentMessage(AgentMessageItem),
Plan(PlanItem),
@@ -83,6 +85,16 @@ pub struct UserMessageItem {
pub content: Vec<UserInput>,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema)]
pub struct FunctionCallOutputItem {
pub id: String,
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub namespace: Option<String>,
pub output: FunctionCallOutputBody,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema, PartialEq, Eq)]
pub struct HookPromptItem {
pub id: String,
@@ -677,6 +689,7 @@ impl TurnItem {
pub fn id(&self) -> String {
match self {
TurnItem::UserMessage(item) => item.id.clone(),
TurnItem::FunctionCallOutput(item) => item.id.clone(),
TurnItem::HookPrompt(item) => item.id.clone(),
TurnItem::AgentMessage(item) => item.id.clone(),
TurnItem::Plan(item) => item.id.clone(),
+1 -1
View File
@@ -520,7 +520,7 @@ impl TurnItem {
pub fn as_legacy_events(&self, show_raw_agent_reasoning: bool) -> Vec<EventMsg> {
match self {
TurnItem::UserMessage(item) => vec![item.as_legacy_event()],
TurnItem::HookPrompt(_) => Vec::new(),
TurnItem::FunctionCallOutput(_) | TurnItem::HookPrompt(_) => Vec::new(),
TurnItem::AgentMessage(item) => item.as_legacy_events(),
TurnItem::Plan(_) => Vec::new(),
TurnItem::CommandExecution(_)