Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .rust-file-sizes.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"baseline": {
"crates/agentic-server-core/src/tool/registry.rs": 515,
"crates/agentic-server-core/src/tool/tool_search.rs": 1078,
"crates/agentic-server-core/src/types/io/output.rs": 1069,
"crates/agentic-server-core/src/types/io/output.rs": 1044,
"crates/agentic-server-core/src/types/tools/params.rs": 537,
"crates/agentic-server/src/agentic_process.rs": 557,
"crates/agentic-server/src/auth.rs": 727
Expand Down
23 changes: 15 additions & 8 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1572,8 +1572,8 @@ round:
permits are acquired. These permit waits do not count toward it, so it is not a
deadline for the entire round or total call latency. Timeout, execution, and tool-config
failures become failed tool outputs that can be fed back to the model instead of
failing the whole response. A tool registered as gateway-owned without an
implementation (currently file search) likewise produces an error tool result.
failing the whole response. A gateway-owned tool without an executable
binding likewise produces an error tool call output.
- Parallel safety is a per-handler contract. `GatewayExecutor::supports_parallel_execution`
defaults to `false`; registration turns that into a `GatewayBinding::self_exclusion`
semaphore. The semaphore serializes only simultaneous calls to the **same
Expand Down Expand Up @@ -2014,9 +2014,8 @@ RequestPayload::to_upstream_request

`RequestPayload::to_upstream_request` is the only request-level seam that prepares
tools for vLLM. New callers must use it rather than rebuilding function schemas or
normalizing declarations in the executor. Declared placeholders that are not yet
supported, currently file search, produce no upstream function declaration until they
have a complete handler and execution path. Code interpreter has a fixed
normalizing declarations in the executor. File search normalizes to a built-in
function contract and binds the request's configured vector stores. Code interpreter has a fixed
normalization contract in every build. Requests that declare it fail before
inference unless the binary includes the `embedded-code-interpreter` feature,
the operator enables it, and Eryx runtime readiness succeeds.
Expand All @@ -2040,18 +2039,26 @@ the operator enables it, and Eryx runtime readiness succeeds.
`ToolDeclaration`. These are the declaration-level validation and normalization entry
points used by `RequestPayload::to_upstream_request` on the converted declarations. Each supported variant's
policy belongs to its corresponding `ToolHandler`: `FunctionHandler`,
`ToolSearchHandler`, `McpHandler`, `WebSearchHandler`, `WebFetchHandler`,
`ToolSearchHandler`, `McpHandler`, `WebSearchHandler`, `WebFetchHandler`, `FileSearchHandler`,
`CodexNamespaceHandler`, `CustomHandler`, or `CodeInterpreterHandler`. Web search's fixed canonical builder
is shared with `WebSearchHandler::normalize`; it remains one schema even though it has no
per-declaration normalization state. The method name is plural because namespace and
MCP declarations may expand to several model-visible function tools.
`FileSearch` remains an unsupported placeholder and normalizes to nothing. The
code interpreter normalizes to one fixed function contract in every build, while
The code interpreter normalizes to one fixed function contract in every build, while
request validation rejects unavailable runtimes before upstream inference.
Feature-enabled builds bind the ready private `CodeInterpreterExecutor` to the provider.
`ToolDeclaration::declares_async` and `declares_unsupported_async` answer which
declarations make a tool async and which carry `async` where it is not accepted;
`GatewayExecutors::validate_declarations` rejects the latter with its declared index.
- **`file_search/`** — `FileSearchHandler` normalizes the built-in declaration and
projects typed call items and citations. Its shared `FileSearchService` handles
file ingestion and semantic, keyword, and hybrid retrieval against
injected `FileSearchRepository` and `FileBlobStore` implementations. The default
repository uses `storage::file_search`, while the default blob store uses local
files. `ExecutionContext::from_config` initializes these defaults; embedders may
install a service with alternate providers. The Files and vector
store HTTP handlers call this same service. See [file search](docs/api/file-search.md) for configuration,
resource limits, and optional PDF ingestion.
- **`async_execution.rs`** — the model-visible hints for async client tools. A vLLM-served
model stops at a call, so the gateway continues the response with the call pending.
`FunctionHandler`, `CustomHandler`, and `CodexNamespaceHandler` normalization append
Expand Down
4 changes: 2 additions & 2 deletions crates/agentic-llm-d/tests/split_execution_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,8 +493,8 @@ async fn a_turn_that_cannot_be_stored_is_refused() {
[
created,
in_progress,
r#"data: {"type":"response.output_item.added","output_index":0,"item":{"type":"file_search_call","id":"fs_1","status":"in_progress","queries":["rust"]}}"#,
r#"data: {"type":"response.output_item.done","output_index":0,"item":{"type":"file_search_call","id":"fs_1","status":"completed","queries":["rust"]}}"#,
r#"data: {"type":"response.output_item.added","output_index":0,"item":{"type":"future_tool_call","id":"future_1","status":"in_progress"}}"#,
r#"data: {"type":"response.output_item.done","output_index":0,"item":{"type":"future_tool_call","id":"future_1","status":"completed"}}"#,
completed,
]
.join("\n\n"),
Expand Down
1 change: 1 addition & 0 deletions crates/agentic-server-core/src/events/normalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ fn extract_payload(event_type: SSEEventType, json: &Value) -> EventPayload {
SSEEventType::ContentPartAdded
| SSEEventType::ReasoningPartAdded
| SSEEventType::ReasoningPartDone
| SSEEventType::FileSearchCallInProgress
| SSEEventType::FileSearchCallSearching
| SSEEventType::FileSearchCallCompleted
| SSEEventType::WebSearchCallInProgress
Expand Down
4 changes: 4 additions & 0 deletions crates/agentic-server-core/src/events/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ pub enum SSEEventType {
ReasoningSummaryTextDone,

// Built-in tool calls
FileSearchCallInProgress,
FileSearchCallSearching,
FileSearchCallCompleted,
WebSearchCallInProgress,
Expand Down Expand Up @@ -120,6 +121,7 @@ impl From<&str> for SSEEventType {
"response.reasoning_part.done" => Self::ReasoningPartDone,
"response.reasoning_summary_text.delta" => Self::ReasoningSummaryTextDelta,
"response.reasoning_summary_text.done" => Self::ReasoningSummaryTextDone,
"response.file_search_call.in_progress" => Self::FileSearchCallInProgress,
"response.file_search_call.searching" => Self::FileSearchCallSearching,
"response.file_search_call.completed" => Self::FileSearchCallCompleted,
"response.web_search_call.in_progress" => Self::WebSearchCallInProgress,
Expand Down Expand Up @@ -176,6 +178,7 @@ impl TryFrom<SSEEventType> for &'static str {
SSEEventType::ReasoningPartDone => Ok("response.reasoning_part.done"),
SSEEventType::ReasoningSummaryTextDelta => Ok("response.reasoning_summary_text.delta"),
SSEEventType::ReasoningSummaryTextDone => Ok("response.reasoning_summary_text.done"),
SSEEventType::FileSearchCallInProgress => Ok("response.file_search_call.in_progress"),
SSEEventType::FileSearchCallSearching => Ok("response.file_search_call.searching"),
SSEEventType::FileSearchCallCompleted => Ok("response.file_search_call.completed"),
SSEEventType::WebSearchCallInProgress => Ok("response.web_search_call.in_progress"),
Expand Down Expand Up @@ -549,6 +552,7 @@ mod tests {
SSEEventType::ReasoningPartDone,
SSEEventType::ReasoningSummaryTextDelta,
SSEEventType::ReasoningSummaryTextDone,
SSEEventType::FileSearchCallInProgress,
SSEEventType::FileSearchCallSearching,
SSEEventType::FileSearchCallCompleted,
SSEEventType::WebSearchCallInProgress,
Expand Down
4 changes: 4 additions & 0 deletions crates/agentic-server-core/src/events/types/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub enum SSEItemType {
ToolSearchCall,
ToolSearchOutput,
CustomToolCall,
FileSearchCall,
WebSearchCall,
McpCall,
McpListTools,
Expand Down Expand Up @@ -40,6 +41,7 @@ impl SSEItemType {
Self::ToolSearchCall => "tool_search_call",
Self::ToolSearchOutput => "tool_search_output",
Self::CustomToolCall => "custom_tool_call",
Self::FileSearchCall => "file_search_call",
Self::WebSearchCall => "web_search_call",
Self::McpCall => "mcp_call",
Self::McpListTools => "mcp_list_tools",
Expand Down Expand Up @@ -70,6 +72,7 @@ impl std::str::FromStr for SSEItemType {
"tool_search_call" => Ok(Self::ToolSearchCall),
"tool_search_output" => Ok(Self::ToolSearchOutput),
"custom_tool_call" => Ok(Self::CustomToolCall),
"file_search_call" => Ok(Self::FileSearchCall),
"web_search_call" => Ok(Self::WebSearchCall),
"mcp_call" => Ok(Self::McpCall),
"mcp_list_tools" => Ok(Self::McpListTools),
Expand All @@ -92,6 +95,7 @@ impl TryFrom<&OutputItem> for SSEItemType {
OutputItem::ToolSearchCall(_) => Ok(Self::ToolSearchCall),
OutputItem::ToolSearchOutput(_) => Ok(Self::ToolSearchOutput),
OutputItem::CustomToolCall(_) => Ok(Self::CustomToolCall),
OutputItem::FileSearchCall(_) => Ok(Self::FileSearchCall),
OutputItem::WebSearchCall(_) => Ok(Self::WebSearchCall),
OutputItem::McpCall(_) => Ok(Self::McpCall),
OutputItem::McpListTools(_) => Ok(Self::McpListTools),
Expand Down
6 changes: 4 additions & 2 deletions crates/agentic-server-core/src/events/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ pub(crate) fn expected_item_type(frame: &EventFrame) -> Option<SSEItemType> {
| SSEEventType::ReasoningPartDone
| SSEEventType::ReasoningSummaryTextDelta
| SSEEventType::ReasoningSummaryTextDone => Some(SSEItemType::Reasoning),
SSEEventType::FileSearchCallInProgress
| SSEEventType::FileSearchCallSearching
| SSEEventType::FileSearchCallCompleted => Some(SSEItemType::FileSearchCall),
SSEEventType::WebSearchCallInProgress
| SSEEventType::WebSearchCallSearching
| SSEEventType::WebSearchCallCompleted => Some(SSEItemType::WebSearchCall),
Expand Down Expand Up @@ -130,8 +133,6 @@ pub(crate) fn expected_item_type(frame: &EventFrame) -> Option<SSEItemType> {
| SSEEventType::ResponseSteerFailed
| SSEEventType::OutputItemAdded
| SSEEventType::OutputItemDone
| SSEEventType::FileSearchCallSearching
| SSEEventType::FileSearchCallCompleted
| SSEEventType::Keepalive
| SSEEventType::Other => None,
}
Expand Down Expand Up @@ -309,6 +310,7 @@ fn validate_event_fields(
| SSEEventType::ResponseSteerFailed
| SSEEventType::OutputItemAdded
| SSEEventType::OutputItemDone
| SSEEventType::FileSearchCallInProgress
| SSEEventType::FileSearchCallSearching
| SSEEventType::FileSearchCallCompleted
| SSEEventType::WebSearchCallInProgress
Expand Down
54 changes: 20 additions & 34 deletions crates/agentic-server-core/src/executor/accumulator/active.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
//! here; nothing in this module resolves indexes or IDs.
mod agent_message;
mod shell;
mod size;
use agent_message::AgentMessageState;
use shell::ShellCallState;

Expand All @@ -21,15 +22,14 @@ use super::active_text::{MessageState, ReasoningState};
use super::completion::MergeDone;
use crate::events::{EventPayload, SSEItemType};
use crate::executor::error::{ExecutorError, ExecutorResult};
use crate::executor::response_budget::{
ExecutorResponseBudget, RETAINED_CONTAINER_OVERHEAD_BYTES, RetainedAccount, RetainedSize,
};
use crate::executor::response_budget::{ExecutorResponseBudget, RetainedAccount, RetainedSize};
use crate::types::event::MessageStatus;
use crate::types::io::output::McpListTools;
use crate::types::io::{
ApplyDone, CodeInterpreterCall, CodeInterpreterCallStatus, CompactionItem, CustomToolCall, FunctionToolCall,
McpCall, McpCallStatus, MultiAgentCall, MultiAgentCallOutput, OutputItem, OutputMessage, ReasoningOutput,
ReasoningStatus, ShellCall, ShellCallStatus, ToolSearchCall, WebSearchCall, WebSearchCallStatus,
ApplyDone, CodeInterpreterCall, CodeInterpreterCallStatus, CompactionItem, CustomToolCall, FileSearchCall,
FileSearchCallStatus, FunctionToolCall, McpCall, McpCallStatus, MultiAgentCall, MultiAgentCallOutput, OutputItem,
OutputMessage, ReasoningOutput, ReasoningStatus, ShellCall, ShellCallStatus, ToolSearchCall, WebSearchCall,
WebSearchCallStatus,
};
use crate::types::tools::ToolSearchStatus;

Expand Down Expand Up @@ -134,6 +134,7 @@ pub(super) enum ActiveItem {
ShellCall(ShellCallState),
CodeInterpreterCall { item: Option<CodeInterpreterCall> },
ToolSearchCall { item: ToolSearchCall },
FileSearchCall { item: Option<FileSearchCall> },
WebSearchCall { item: Option<WebSearchCall> },
McpCall { item: McpCall },
McpListTools { item: McpListTools },
Expand Down Expand Up @@ -182,6 +183,7 @@ impl ActiveItem {
arguments: String::with_capacity(128),
}),
SSEItemType::CodeInterpreterCall => Self::CodeInterpreterCall { item: None },
SSEItemType::FileSearchCall => Self::FileSearchCall { item: None },
SSEItemType::ToolSearchCall => Self::ToolSearchCall {
item: ToolSearchCall::try_from(payload).ok()?,
},
Expand Down Expand Up @@ -220,6 +222,7 @@ impl ActiveItem {
arguments: String::new(),
}),
OutputItem::CodeInterpreterCall(item) => Self::CodeInterpreterCall { item: Some(item) },
OutputItem::FileSearchCall(item) => Self::FileSearchCall { item: Some(item) },
OutputItem::ToolSearchCall(item) => Self::ToolSearchCall { item },
OutputItem::CustomToolCall(item) => Self::CustomToolCall(CustomToolCallState {
item,
Expand All @@ -245,6 +248,7 @@ impl ActiveItem {
Self::CustomToolCall(state) => Some(&state.item.id),
Self::ShellCall(state) => state.item.id.as_deref(),
Self::CodeInterpreterCall { item } => item.as_ref().map(|item| item.id.as_str()),
Self::FileSearchCall { item } => item.as_ref().map(|item| item.id.as_str()),
Self::ToolSearchCall { item } => Some(&item.id),
Self::WebSearchCall { item } => item.as_ref().map(|item| item.id.as_str()),
Self::McpCall { item } => Some(&item.id),
Expand All @@ -262,6 +266,7 @@ impl ActiveItem {
Self::Reasoning(_) => SSEItemType::Reasoning,
Self::FunctionCall(_) => SSEItemType::FunctionCall,
Self::CodeInterpreterCall { .. } => SSEItemType::CodeInterpreterCall,
Self::FileSearchCall { .. } => SSEItemType::FileSearchCall,
Self::ToolSearchCall { .. } => SSEItemType::ToolSearchCall,
Self::CustomToolCall(_) => SSEItemType::CustomToolCall,
Self::ShellCall(_) => SSEItemType::ShellCall,
Expand Down Expand Up @@ -298,6 +303,7 @@ impl ActiveItem {
Self::CustomToolCall(state) => state.apply(payload, account, budget),
Self::ShellCall(state) => state.apply(payload, account, budget),
Self::CodeInterpreterCall { .. }
| Self::FileSearchCall { .. }
| Self::MultiAgentCall { .. }
| Self::MultiAgentCallOutput { .. }
| Self::ToolSearchCall { .. }
Expand Down Expand Up @@ -346,6 +352,12 @@ impl ActiveItem {
| CodeInterpreterCallStatus::Failed
| CodeInterpreterCallStatus::Incomplete
),
(Self::FileSearchCall { .. }, Some(OutputItem::FileSearchCall(done))) => {
matches!(
done.status,
FileSearchCallStatus::Completed | FileSearchCallStatus::Incomplete | FileSearchCallStatus::Failed
)
}
(Self::WebSearchCall { .. }, Some(OutputItem::WebSearchCall(done))) => {
matches!(
done.status,
Expand Down Expand Up @@ -418,6 +430,7 @@ impl ActiveItem {
(Self::CodeInterpreterCall { item }, Some(OutputItem::CodeInterpreterCall(done))) => {
item.merge_done(done, item_id);
}
(Self::FileSearchCall { item }, Some(OutputItem::FileSearchCall(done))) => *item = Some(done.clone()),
(Self::ToolSearchCall { item }, Some(OutputItem::ToolSearchCall(done))) => item.merge_done(done, ()),
(Self::CustomToolCall(state), Some(OutputItem::CustomToolCall(done))) => {
state.item.merge_done(done, &mut state.input);
Expand All @@ -444,6 +457,7 @@ impl ActiveItem {
Self::Reasoning(state) => OutputItem::Reasoning(state.item),
Self::FunctionCall(state) => state.finalize(),
Self::CodeInterpreterCall { item } => OutputItem::CodeInterpreterCall(item?),
Self::FileSearchCall { item } => OutputItem::FileSearchCall(item?),
Self::ToolSearchCall { item } => OutputItem::ToolSearchCall(item),
Self::Message(state) => state.finalize(),
Self::CustomToolCall(state) => state.finalize(),
Expand All @@ -457,31 +471,3 @@ impl ActiveItem {
})
}
}

/// Bytes an in-flight item retains: its typed item plus its streaming buffers.
/// Measures the same fields as the completed item so reconciliation at
/// completion charges only what streaming could not know in advance.
impl RetainedSize for ActiveItem {
fn retained_bytes(&self) -> usize {
match self {
Self::Message(state) => state.retained_bytes(),
Self::Reasoning(state) => state.retained_bytes(),
Self::FunctionCall(state) => state.retained_bytes(),
Self::CustomToolCall(state) => state.retained_bytes(),
Self::ShellCall(state) => state.retained_bytes(),
Self::CodeInterpreterCall { item } => item
.as_ref()
.map_or(RETAINED_CONTAINER_OVERHEAD_BYTES, RetainedSize::retained_bytes),
Self::ToolSearchCall { item } => item.retained_bytes(),
Self::WebSearchCall { item } => item
.as_ref()
.map_or(RETAINED_CONTAINER_OVERHEAD_BYTES, RetainedSize::retained_bytes),
Self::McpCall { item } => item.retained_bytes(),
Self::McpListTools { item } => item.retained_bytes(),
Self::Compaction { item } => item.retained_bytes(),
Self::MultiAgentCall { item } => item.retained_bytes(),
Self::MultiAgentCallOutput { item } => item.retained_bytes(),
Self::AgentMessage(state) => state.retained_bytes(),
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//! Retained-size accounting for streaming output items.

use super::ActiveItem;
use crate::executor::response_budget::{RETAINED_CONTAINER_OVERHEAD_BYTES, RetainedSize};

/// Bytes an in-flight item retains: its typed item plus its streaming buffers.
/// Measures the same fields as the completed item so reconciliation at
/// completion charges only what streaming could not know in advance.
impl RetainedSize for ActiveItem {
fn retained_bytes(&self) -> usize {
match self {
Self::Message(state) => state.retained_bytes(),
Self::Reasoning(state) => state.retained_bytes(),
Self::FunctionCall(state) => state.retained_bytes(),
Self::CustomToolCall(state) => state.retained_bytes(),
Self::ShellCall(state) => state.retained_bytes(),
Self::CodeInterpreterCall { item } => item
.as_ref()
.map_or(RETAINED_CONTAINER_OVERHEAD_BYTES, RetainedSize::retained_bytes),
Self::FileSearchCall { item } => item
.as_ref()
.map_or(RETAINED_CONTAINER_OVERHEAD_BYTES, RetainedSize::retained_bytes),
Self::ToolSearchCall { item } => item.retained_bytes(),
Self::WebSearchCall { item } => item
.as_ref()
.map_or(RETAINED_CONTAINER_OVERHEAD_BYTES, RetainedSize::retained_bytes),
Self::McpCall { item } => item.retained_bytes(),
Self::McpListTools { item } => item.retained_bytes(),
Self::Compaction { item } => item.retained_bytes(),
Self::MultiAgentCall { item } => item.retained_bytes(),
Self::MultiAgentCallOutput { item } => item.retained_bytes(),
Self::AgentMessage(state) => state.retained_bytes(),
}
}
}
Loading
Loading