Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ fn make_request(input: &str, stream: bool, prev_id: Option<String>) -> RequestPa
max_output_tokens: None,
truncation: None,
metadata: None,
parallel_tool_calls: None,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ mod tests {
max_output_tokens: None,
truncation: None,
metadata: None,
parallel_tool_calls: None,
};
RequestContext {
enriched_request: req.clone(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ mod tests {
max_output_tokens: None,
truncation: None,
metadata: None,
parallel_tool_calls: None,
};
RequestContext {
enriched_request: req.clone(),
Expand Down
18 changes: 18 additions & 0 deletions crates/agentic-server-core/src/types/request_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub struct RequestPayload {
pub max_output_tokens: Option<u32>,
pub truncation: Option<String>,
pub metadata: Option<Value>,
pub parallel_tool_calls: Option<bool>,
}

fn default_true() -> bool {
Expand Down Expand Up @@ -61,6 +62,8 @@ pub struct UpstreamRequest<'a> {
pub truncation: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<&'a Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parallel_tool_calls: Option<bool>,
}

// serde's `skip_serializing_if` requires a `&Option<T>` receiver, so the
Expand Down Expand Up @@ -109,6 +112,7 @@ impl RequestPayload {
max_output_tokens: self.max_output_tokens,
truncation: self.truncation.as_deref(),
metadata: self.metadata.as_ref(),
parallel_tool_calls: self.parallel_tool_calls,
})
}
}
Expand Down Expand Up @@ -214,6 +218,20 @@ mod tests {
assert_eq!(value["input"], "hi");
}

#[test]
fn to_upstream_request_preserves_parallel_tool_calls() {
let payload: RequestPayload = serde_json::from_value(serde_json::json!({
"model": "test",
"input": "hi",
"parallel_tool_calls": false
}))
.unwrap();

let upstream = payload.to_upstream_request(false).expect("valid upstream request");
let value = serde_json::to_value(upstream).unwrap();
assert_eq!(value["parallel_tool_calls"], false);
}

#[test]
fn to_upstream_request_flattens_namespace_and_skips_unknown_tools() {
let payload: RequestPayload = serde_json::from_value(serde_json::json!({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ fn request(text: &str, tools: Option<Vec<ResponsesTool>>) -> RequestPayload {
max_output_tokens: Some(1024),
truncation: None,
metadata: None,
parallel_tool_calls: None,
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/agentic-server-core/tests/support/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ pub fn make_request(
max_output_tokens: None,
truncation: None,
metadata: None,
parallel_tool_calls: None,
}
}

Expand Down
16 changes: 16 additions & 0 deletions crates/agentic-server-core/tests/web_search_tool_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,7 @@ async fn execute_runs_web_search_and_sends_tool_output_back_to_model() {
max_output_tokens: Some(1024),
truncation: None,
metadata: None,
parallel_tool_calls: None,
};

let result = ExecuteRequest::new(payload, Arc::clone(&exec_ctx)).run().await.unwrap();
Expand Down Expand Up @@ -667,6 +668,7 @@ async fn execute_relaxes_forced_tool_choice_after_web_search_result() {
max_output_tokens: Some(1024),
truncation: None,
metadata: None,
parallel_tool_calls: None,
};

let result = ExecuteRequest::new(payload, Arc::clone(&exec_ctx)).run().await.unwrap();
Expand Down Expand Up @@ -714,6 +716,7 @@ async fn execute_returns_mixed_client_tool_calls_without_followup_model_request(
max_output_tokens: Some(1024),
truncation: None,
metadata: None,
parallel_tool_calls: None,
};

let result = ExecuteRequest::new(payload, Arc::clone(&exec_ctx)).run().await.unwrap();
Expand Down Expand Up @@ -760,6 +763,7 @@ async fn execute_returns_mixed_client_tool_calls_without_followup_model_request(
max_output_tokens: Some(1024),
truncation: None,
metadata: None,
parallel_tool_calls: None,
};
let continuation = ExecuteRequest::new(continuation_payload, exec_ctx).run().await.unwrap();
assert!(matches!(continuation, Either::Left(_)));
Expand Down Expand Up @@ -829,6 +833,7 @@ async fn execute_accumulates_usage_across_web_search_model_rounds() {
max_output_tokens: Some(1024),
truncation: None,
metadata: None,
parallel_tool_calls: None,
};

let result = ExecuteRequest::new(payload, exec_ctx).run().await.unwrap();
Expand Down Expand Up @@ -871,6 +876,7 @@ async fn stream_emits_web_search_lifecycle_events_before_final_payload() {
max_output_tokens: Some(1024),
truncation: None,
metadata: None,
parallel_tool_calls: None,
};

let result = ExecuteRequest::new(payload, Arc::clone(&exec_ctx)).run().await.unwrap();
Expand Down Expand Up @@ -952,6 +958,7 @@ async fn stream_hides_web_search_function_events_when_name_arrives_on_done() {
max_output_tokens: Some(1024),
truncation: None,
metadata: None,
parallel_tool_calls: None,
};

let result = ExecuteRequest::new(payload, Arc::clone(&exec_ctx)).run().await.unwrap();
Expand Down Expand Up @@ -1017,6 +1024,7 @@ async fn execute_runs_multiple_web_search_calls_concurrently() {
max_output_tokens: Some(1024),
truncation: None,
metadata: None,
parallel_tool_calls: None,
};

let result = tokio::time::timeout(Duration::from_secs(2), ExecuteRequest::new(payload, exec_ctx).run())
Expand Down Expand Up @@ -1064,6 +1072,7 @@ async fn execute_feeds_web_search_execution_errors_back_to_model() {
max_output_tokens: Some(1024),
truncation: None,
metadata: None,
parallel_tool_calls: None,
};

let result = ExecuteRequest::new(payload, exec_ctx).run().await.unwrap();
Expand Down Expand Up @@ -1113,6 +1122,7 @@ async fn execute_returns_incomplete_after_max_gateway_tool_rounds() {
max_output_tokens: Some(1024),
truncation: None,
metadata: None,
parallel_tool_calls: None,
};

// Budget exhausted while the model keeps requesting tools → the response is
Expand Down Expand Up @@ -1162,6 +1172,7 @@ async fn execute_feeds_invalid_web_search_arguments_back_to_model() {
max_output_tokens: Some(1024),
truncation: None,
metadata: None,
parallel_tool_calls: None,
};

let result = ExecuteRequest::new(payload, exec_ctx).run().await.unwrap();
Expand Down Expand Up @@ -1218,6 +1229,7 @@ async fn execute_runs_large_gateway_fanout_without_hard_cap() {
max_output_tokens: Some(1024),
truncation: None,
metadata: None,
parallel_tool_calls: None,
};

let result = ExecuteRequest::new(payload, exec_ctx)
Expand Down Expand Up @@ -1280,6 +1292,7 @@ async fn stream_error_events_escape_error_messages() {
max_output_tokens: Some(1024),
truncation: None,
metadata: None,
parallel_tool_calls: None,
};

let result = ExecuteRequest::new(payload, exec_ctx).run().await.unwrap();
Expand Down Expand Up @@ -1354,6 +1367,7 @@ async fn incomplete_turn_persists_a_consistent_conversation_for_continuation() {
max_output_tokens: Some(1024),
truncation: None,
metadata: None,
parallel_tool_calls: None,
};

let result = ExecuteRequest::new(payload, Arc::clone(&exec_ctx)).run().await.unwrap();
Expand All @@ -1379,6 +1393,7 @@ async fn incomplete_turn_persists_a_consistent_conversation_for_continuation() {
max_output_tokens: Some(1024),
truncation: None,
metadata: None,
parallel_tool_calls: None,
};
let _ = ExecuteRequest::new(continuation_payload, exec_ctx).run().await.unwrap();

Expand Down Expand Up @@ -1448,6 +1463,7 @@ async fn stream_returns_incomplete_after_max_gateway_tool_rounds() {
max_output_tokens: Some(1024),
truncation: None,
metadata: None,
parallel_tool_calls: None,
};

let result = ExecuteRequest::new(payload, exec_ctx).run().await.unwrap();
Expand Down
28 changes: 28 additions & 0 deletions crates/agentic-server/tests/responses_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,34 @@ async fn test_store_false_with_web_search_reaches_executor() {
assert_eq!(requests[0]["tools"][0]["name"], "web_search");
}

#[tokio::test]
async fn test_gateway_normalization_preserves_parallel_tool_calls() {
// Arrange
let (llm_url, requests, _h1) = spawn_mock_vllm_json_capture().await;
let (gw_url, _h2) = spawn_gateway(test_state(&test_config(&llm_url))).await;

// Act
let resp = reqwest::Client::new()
.post(format!("{gw_url}/v1/responses"))
.json(&serde_json::json!({
"model": "test",
"input": [{"type": "message", "role": "user", "content": "hi"}],
"tools": [{"type": "web_search_preview"}],
"parallel_tool_calls": false,
"store": false,
"stream": false
}))
.send()
.await
.unwrap();

// Assert
assert_eq!(resp.status(), 200);
let requests = requests.lock().await;
assert_eq!(requests.len(), 1);
assert_eq!(requests[0]["parallel_tool_calls"], false);
}

#[tokio::test]
async fn test_store_false_proxies_large_json_body_to_vllm() {
// Arrange
Expand Down